> ## Documentation Index
> Fetch the complete documentation index at: https://developer.upsun.com/llms.txt
> Use this file to discover all available pages before exploring further.

# PHP-FPM sizing

> Learn how to adjust the maximum number of PHP-FPM workers for your app

export const DynamicCodeBlock = ({language = 'yaml', filename, icon, lines, wrap, expandable, highlight, focus, children}) => {
  const STORAGE_KEY = 'upsun_versions_cache';
  const COMPOSABLE_STORAGE_KEY = 'upsun_composable_cache';
  const CACHE_TTL = 5 * 60 * 1000;
  const API_URL = 'https://meta.upsun.com/images';
  const COMPOSABLE_API_URL = 'https://meta.upsun.com/composable';
  const DEBUG_PREFIX = '[DynamicCodeBlock cache]';
  const [versionData, setVersionData] = useState(null);
  const [versionError, setVersionError] = useState(false);
  const [composableData, setComposableData] = useState(null);
  const [composableError, setComposableError] = useState(false);
  useEffect(() => {
    const fetchData = async () => {
      let cachedData = null;
      let cachedEtag = null;
      if (typeof localStorage !== 'undefined') {
        try {
          const cached = localStorage.getItem(STORAGE_KEY);
          if (cached) {
            const parsed = JSON.parse(cached);
            cachedData = parsed?.data || null;
            cachedEtag = parsed?.etag || null;
            if (cachedData && Date.now() - parsed.timestamp < CACHE_TTL) {
              return cachedData;
            }
          }
        } catch (err) {
          console.error('Failed to load from cache:', err);
        }
      }
      const requestHeaders = cachedEtag ? {
        'If-None-Match': cachedEtag
      } : {};
      console.debug(`${DEBUG_PREFIX} revalidating`, {
        storageKey: STORAGE_KEY,
        hasCachedData: Boolean(cachedData),
        hasCachedEtag: Boolean(cachedEtag)
      });
      const response = await fetch(API_URL, {
        headers: requestHeaders
      });
      if (response.status === 304 && cachedData) {
        console.debug(`${DEBUG_PREFIX} revalidated (304)`, {
          storageKey: STORAGE_KEY
        });
        if (typeof localStorage !== 'undefined') {
          try {
            const etag = response.headers.get('etag') || cachedEtag;
            localStorage.setItem(STORAGE_KEY, JSON.stringify({
              data: cachedData,
              etag,
              timestamp: Date.now()
            }));
          } catch (err) {
            console.error('Failed to refresh cache metadata:', err);
          }
        }
        return cachedData;
      }
      if (!response.ok) throw new Error(`API request failed: ${response.statusText}`);
      const data = await response.json();
      const etag = response.headers.get('etag');
      console.debug(`${DEBUG_PREFIX} refreshed (200)`, {
        storageKey: STORAGE_KEY,
        etag
      });
      if (typeof localStorage !== 'undefined') {
        try {
          localStorage.setItem(STORAGE_KEY, JSON.stringify({
            data,
            etag,
            timestamp: Date.now()
          }));
        } catch (err) {
          console.error('Failed to cache data:', err);
        }
      }
      return data;
    };
    fetchData().then(data => setVersionData(data)).catch(err => console.error('Failed to fetch version data:', err));
  }, []);
  const findHighestVersion = versionsMap => {
    if (!versionsMap || Object.keys(versionsMap).length === 0) return null;
    const entries = Object.entries(versionsMap);
    const active = entries.filter(([, v]) => v.upsun && v.upsun.status === 'supported' || v.upsun && v.upsun.status === 'deprecated');
    const candidates = active.length > 0 ? active : entries;
    let [highestName] = candidates[0];
    for (let i = 1; i < candidates.length; i++) {
      const [currentName] = candidates[i];
      const cp = currentName.split('.').map(Number);
      const hp = highestName.split('.').map(Number);
      for (let j = 0; j < Math.max(cp.length, hp.length); j++) {
        if ((cp[j] || 0) > (hp[j] || 0)) {
          highestName = currentName;
          break;
        } else if ((cp[j] || 0) < (hp[j] || 0)) {
          break;
        }
      }
    }
    return highestName;
  };
  const getVersion = (lang, requestedVersion = 'latest') => {
    if (lang === 'composable') {
      if (!composableData || !composableData.versions || Object.keys(composableData.versions).length === 0) return null;
      if (requestedVersion && requestedVersion !== 'latest') {
        return (requestedVersion in composableData.versions) ? requestedVersion : null;
      }
      return findHighestVersion(composableData.versions);
    }
    if (!versionData) return null;
    const imageData = versionData[lang];
    if (!imageData || !imageData.versions || Object.keys(imageData.versions).length === 0) {
      return null;
    }
    if (requestedVersion && requestedVersion !== 'latest') {
      return (requestedVersion in imageData.versions) ? requestedVersion : null;
    }
    return findHighestVersion(imageData.versions);
  };
  let code = typeof children === 'string' ? children : String(children || '');
  const codeLines = code.split('\n');
  while (codeLines.length > 0 && codeLines[0].trim() === '') codeLines.shift();
  while (codeLines.length > 0 && codeLines[codeLines.length - 1].trim() === '') codeLines.pop();
  if (codeLines.length > 0) {
    const indents = codeLines.filter(line => line.trim().length > 0).map(line => line.match(/^[ \t]*/)[0].length);
    const minIndent = Math.min(...indents);
    code = codeLines.map(line => line.slice(minIndent)).join('\n');
  }
  code = code.replace(/\{\{version:(.*?)\}\}/g, (match, params) => {
    const parts = params.split(':');
    const lang = parts[0];
    const ver = parts[1] || 'latest';
    const isComposable = lang === 'composable';
    const hasError = isComposable ? composableError : versionError;
    const dataReady = isComposable ? composableData !== null : versionData !== null;
    if (hasError) return '(unavailable)';
    if (dataReady) {
      const resolvedVersion = getVersion(lang, ver);
      return resolvedVersion || match;
    }
    return '...';
  });
  const codeBlockProps = {
    language,
    ...filename && ({
      filename
    }),
    ...icon && ({
      icon
    }),
    ...lines !== undefined && ({
      lines
    }),
    ...wrap !== undefined && ({
      wrap
    }),
    ...expandable !== undefined && ({
      expandable
    }),
    ...highlight && ({
      highlight
    }),
    ...focus && ({
      focus
    })
  };
  return <CodeBlock {...codeBlockProps}>{code}</CodeBlock>;
};

export const DisclaimerNix = () => <Tip>
    You can now use composable image to install runtimes and tools in your application container. To find out more, see the <a href="/docs/configure-apps/app-reference/composable-image">Composable image</a> topic.
  </Tip>;

<DisclaimerNix />

PHP-FPM helps improve your app's performance
by maintaining pools of workers that can process PHP requests.
This is particularly useful when your app needs to handle a high number of simultaneous requests.

By default, Upsun automatically sets a maximum number of PHP-FPM workers for your app.
This number is calculated based on three parameters:

* The container memory: the amount of memory you can allot for PHP processing
  depending on [your defined application resources](/docs/manage-resources).
* The request memory: the amount of memory an average PHP request is expected to require.
* The reserved memory: the amount of memory you need to reserve for tasks that aren't related to requests.
  The number is calculated as follows: <img src="https://mintcdn.com/upsun-c9761871/tdb1nLmHEPAmx6I9/images/php/PHP-FPM-Workers-Calculation.png?fit=max&auto=format&n=tdb1nLmHEPAmx6I9&q=85&s=3b31a2e0bbf8e505a9ac131306c53f00" alt="The sum of container memory minus reserved memory divided by request memory" width="359" height="73" data-path="images/php/PHP-FPM-Workers-Calculation.png" />

Note that when the resulting number is a decimal,
it's rounded up to set the maximum number of workers.
Also, the minimum number of PHP-FPM workers is 2.

<Note>
  To ensure that Upsun doesn't add more workers than the CPU can handle,
  a CPU limit applies as soon as the number of set workers equals or exceeds 25.
  This limit is calculated as follows: `number of vCPU cores * 5`.

  For example, if you have a 2XL container with 8 CPU cores,
  you can have up to 40 workers as long as you have sufficient memory.
</Note>

To adjust the maximum number of PHP-FPM workers depending on your app's needs, follow the instructions on this page.

## Before you begin

You need:

* An up-and-running web app in PHP, complete with [PHP-FPM](https://www.php.net/manual/en/install.fpm.php)
* The [Upsun CLI](/cli)

Note that the memory settings mentioned on this page are different from the [`memory_limit` PHP setting](/docs/languages/php).
The `memory_limit` setting is the maximum amount of memory a single PHP process can use
before it's automatically terminated.

## 1. Estimate the optimal request memory for your app

To determine what the optimal request memory is for your app,
you can refer to your PHP access logs.
Run a command similar to:

```bash theme={null}
upsun log --lines 5000 php.access | awk '{print $6}' | sort -n | uniq -c
```

This command takes into account the last 5,000 requests that reached PHP-FPM.
You can adjust this number depending on the amount of traffic on your site.

In the output, you can see how many requests used how much memory, in KB.
For example:

```bash theme={null}
    2654 2048
    431  4096
    584  8192
    889  10240
    374  12288
     68  46384
```

The output shows that:

* The majority of requests peaked at 2,048 KB of memory.
* Most other requests used up to around 10 MB of memory.
* A few requests used up to around 12 MB of memory.
* Only 68 requests peaked at around 46 MB of memory.

In this situation, you might want to be cautious
and [set your request memory](#2-adjust-the-maximum-number-of-php-fpm-workers) to 12 MB.
Setting a lower request memory presents a risk of allowing more concurrent requests.
This can result in memory swapping and latencies.

## 2. Adjust the maximum number of PHP-FPM workers

By default, the request memory is set to 45 MB
and the reserved memory is set to 70 MB.
These values allow most programs to run,
but you can amend them to fit your needs.

To do so, adjust your [app configuration](/docs/configure-apps).
Under `runtime` in the [`sizing_hints` setting](/docs/configure-apps/app-reference/single-runtime-image#sizing-hints),
set the values for `reserved_memory` and `request_memory`.

For example,
if you estimate your [optimal request memory](#1-estimate-the-optimal-request-memory-for-your-app) to be 35 MB
and your reserved memory to be 80 MB,
you can use:

<DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
  {`
      applications:
        myapp:
          type: 'php:{{version:php:latest}}'
          runtime:
            sizing_hints:
              request_memory: 35 # LESS memory per request == MORE workers.
              reserved_memory: 80`
  }
</DynamicCodeBlock>

Note that the minimum value for the `request_memory` key is 10 MB
and the minimum value for the `reserved_memory` key is 70 MB.
If you set lower values,
they're automatically overridden with those minimums.

To check the maximum number of PHP-FPM workers available to your app,
run the following command, where `children` refers to PHP-FPM workers:

```bash theme={null}
upsun ssh "grep -e '^pm.max_children' /etc/php/*/fpm/php-fpm.conf"
```

You get output similar to the following:

```bash theme={null}
pm.max_children = 5
```
