App Deployment·8 min read·

php.ini Knobs: Memory, Uploads, and Opcache

Tune the few php.ini knobs that matter in production: memory_limit, upload sizes, and opcache, so a 2 GB VPS serves PHP without swap or silent upload failures.

NB

Netbay Developer Relations

Netbay Engineering

On this page

PHP ships with hundreds of php.ini directives. Most of them should stay at distro defaults. Production pain comes from three clusters: how much RAM a single request may eat, how large a POST the interpreter will accept, and whether opcache is actually compiling your code into shared memory. Get those wrong on a 2 GB Ubuntu VPS and you swap, reject legitimate uploads, or recompile every request like it is 2009.

Ubuntu splits CLI and FPM configs. Editing /etc/php/8.3/cli/php.ini does nothing for nginx traffic. FPM reads /etc/php/8.3/fpm/php.ini plus pool overrides. Confirm the loaded file with php-fpm8.3 -i or a temporary phpinfo, then change the FPM copy and reload php8.3-fpm. Reloading nginx is not enough.

The three php.ini clusters that matter memory_limit per request cap 256M typical app 512M image jobs times max_children upload / post upload_max_filesize post_max_size nginx client_max_body all three must agree opcache enable=1 interned validate_timestamps memory_consumption shared, not per child Edit FPM php.ini, then systemctl reload php8.3-fpm. CLI is a different file.

memory_limit is a per-request cap, not a server cap

memory_limit is the ceiling for one PHP process during one request. It is not the total RAM PHP may use. Ten FPM workers at 256M can theoretically touch 2.5 GB before the kernel steps in. Size it for the fattest legitimate request, then size the pool so workers times that ceiling still fit in RAM after nginx, MySQL, and the OS take their share.

256M is a sane default for Laravel and WordPress on a 2 GB plan. Raise to 512M if you generate PDFs, resize large images in-process, or run Composer as the FPM user (you should not). Do not set -1 in production. Unlimited memory turns a leaked query into a host-wide OOM.

CLI can use a higher limit than FPM. Queue workers and artisan commands often need more headroom than a web request. That is why Ubuntu splits the files. Keep FPM strict and give CLI 512M if deploy scripts allocate a lot.

On Intel Xeon Platinum hosts with High-Speed SSD, swapping is still a failure mode. SSD pages in faster than rust, but a PHP app that lives in swap is an app whose p99 latency has already died. Watch RSS per FPM worker with ps and tune memory_limit down if workers sit far below the cap.

Uploads fail unless three settings agree

upload_max_filesize caps a single uploaded file. post_max_size caps the entire POST body, so it must be larger than upload_max_filesize plus form fields. nginx client_max_body_size caps the HTTP body before PHP ever sees it. If nginx is 1m and PHP is 32M, users get 413 from nginx and you will never find a PHP error.

Set all three together. 32M covers most CMS media. 64M is plenty for typical admin uploads. If you need video, do not push 2 GB through PHP. Use a signed direct upload or a dedicated media path.

ini
memory_limit = 256M
upload_max_filesize = 32M
post_max_size = 40M
max_execution_time = 60
max_input_time = 60
max_file_uploads = 20
display_errors = Off
log_errors = On
error_log = /var/log/php8.3-fpm.log
expose_php = Off
date.timezone = Asia/Kolkata

max_execution_time and max_input_time stop runaway requests. 60 seconds is enough for web traffic. Long jobs belong in a queue, not in FPM. display_errors must be Off on the public internet. log_errors must be On. expose_php Off removes the X-Powered-By header from PHP itself; also hide it in nginx.

Timezone should be explicit. Asia/Kolkata matches a Lucknow VPS and keeps Laravel timestamps aligned with IST without hoping the OS timezone leaked in.

Opcache is the free performance win

Without opcache, every request reparses PHP files from disk. With opcache, bytecode lives in shared memory and FPM workers reuse it. On High-Speed SSD the parse is not slow in isolation, but under concurrency it dominates CPU. Enable it. Confirm it with opcache_get_status() in a one-off script, then delete the script.

ini
opcache.enable = 1
opcache.enable_cli = 0
opcache.memory_consumption = 128
opcache.interned_strings_buffer = 16
opcache.max_accelerated_files = 20000
opcache.validate_timestamps = 0
opcache.revalidate_freq = 0
opcache.save_comments = 1
opcache.jit = disable

memory_consumption is megabytes of shared memory, not per worker. 128M holds a Laravel app and a decent plugin set. WordPress with many plugins may want 256M. If hit_rate in opcache_get_status sits below 99 percent and max_cached_keys is exhausted, raise max_accelerated_files.

validate_timestamps = 0 is the production setting. PHP will not stat files to see if they changed. Deploys must reload FPM (systemctl reload php8.3-fpm) so workers pick up new bytecode. In staging, set validate_timestamps = 1 and revalidate_freq = 2 so saves show up without a reload.

JIT is still optional. For typical request/response apps it rarely wins enough to justify the extra RAM. Leave it disabled unless you have a CPU-bound CLI workload and numbers that say otherwise. save_comments stays on because Doctrine and some annotation libraries still read docblocks from opcache.

Reload FPM, then verify

After editing, run php-fpm8.3 -t and systemctl reload php8.3-fpm. A syntax error in php.ini will prevent FPM from starting; test first. Confirm with a CLI that points at the FPM ini if you must, but the ground truth is a request through nginx.

Do not scatter ini_set calls through the app to paper over php.ini. Exceptions exist (raising memory for one export action), but upload limits and opcache cannot be fixed from userland after the request has started. Keep the source of truth in the FPM ini and in the pool file.

Pool-level php_admin_value can override php.ini for one pool. That is useful when two apps share a box and need different memory_limit values. Prefer one pool per app rather than a maze of overrides.

Takeaway

You do not need to memorize php.ini. Set a real memory_limit, align upload and POST and nginx body size, turn opcache on with timestamp validation off, and reload FPM on every deploy. Those knobs decide whether a 2 GB VPS feels fast or thrashes.

You can spin up an Ubuntu 24.04 instance on Netbay in under 60 seconds, edit FPM php.ini on High-Speed SSD, and measure the difference — netbayhosts.in.

Keep reading

Follow along on a real VPS

Deploy Linux in under 60 seconds

These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.

Deploy an instance