Castopod "Max size is 2MiB" — Stock PHP Upload Limits Resurface After a Server Rebuild¶
TL;DR — Debian/Ubuntu ship PHP with
upload_max_filesize = 2Mandpost_max_size = 8M. Any PHP app that accepts real files (Castopod episode audio, Nextcloud, WordPress media) needs those raised — and that tuning is exactly the kind of config that silently evaporates when a host is migrated or rebuilt from packages. Fix it with a conf.d drop-in (survives package upgrades), not by editingphp.ini, and verify through the FPM socket, not the CLI — the two SAPIs read different configs.
Symptom¶
Castopod's upload form rejects a file with:
Your file size is too big! Max size is 2MiB. Increase the
memory_limit,upload_max_filesizeandpost_max_sizevalues in your php configuration file then restart your web server to upload your file.
Castopod isn't enforcing its own limit here — it's reading PHP's upload_max_filesize and reporting it back. Other apps show the same root cause differently: WordPress caps the media-upload size, Nextcloud shows a max-upload notice, plain PHP apps fail with UPLOAD_ERR_INI_SIZE.
Root cause¶
Two things combine:
- Distro defaults are tiny. Debian/Ubuntu PHP packages ship
upload_max_filesize = 2M,post_max_size = 8M. Fine for form posts, useless for podcast episodes. - The tuning was config drift, not managed state. The original server had the limits raised at some point — by hand, in
php.inior a panel. When the host was migrated to a new VM and the LAMP stack was reinstalled from packages, PHP came back at stock defaults and nothing re-applied the override. The gap stays invisible until the first upload larger than 2 MB, which can be weeks after cutover.
This is a general migration gotcha: any hand-tuned php.ini value is lost on a rebuild unless it lives in configuration management or a documented drop-in.
Diagnosis¶
Check what the web SAPI (FPM) actually has loaded. Quick first pass via CLI php -i works for a hint, but CLI and FPM read separate ini trees (/etc/php/8.4/cli/ vs /etc/php/8.4/fpm/), and CLI defaults differ (e.g. CLI memory_limit = -1). To query the live FPM pool directly:
apt install libfcgi-bin # provides cgi-fcgi
echo '<?php echo ini_get("upload_max_filesize")." / ".ini_get("post_max_size")." / ".ini_get("memory_limit");' > /tmp/limits.php
SCRIPT_FILENAME=/tmp/limits.php REQUEST_METHOD=GET \
cgi-fcgi -bind -connect /run/php/php8.4-fpm.sock
rm /tmp/limits.php
Stock output confirming the problem:
Fix¶
Create a drop-in in the FPM conf.d directory instead of editing php.ini — package upgrades can prompt/replace php.ini, but they leave conf.d alone, and a separately named file documents itself:
cat > /etc/php/8.4/fpm/conf.d/99-castopod-uploads.ini <<'EOF'
; Castopod upload limits
upload_max_filesize = 512M
post_max_size = 512M
memory_limit = 512M
max_execution_time = 300
EOF
php-fpm8.4 -t # config test first
systemctl restart php8.4-fpm apache2
Sizing notes:
post_max_sizemust be ≥upload_max_filesize(the whole request body has to fit).memory_limitdoes not need to exceed the upload size for a plain file upload (PHP streams uploads to a temp file), but image/audio post-processing can chew memory — 512M is a comfortable ceiling for Castopod on a 4 GB box.max_execution_time = 300covers slow client uplinks on large episodes.- 512M comfortably fits multi-hour episode audio; scale to your content.
Re-run the cgi-fcgi check — it should now report 512M / 512M / 512M — then retry the upload in the app.
Layered limits to remember¶
PHP is only one gate. If raising it doesn't help, the request may be dying earlier:
| Layer | Directive | Default |
|---|---|---|
| PHP | upload_max_filesize / post_max_size |
2M / 8M |
| nginx (if used) | client_max_body_size |
1M — nginx fails first with 413 |
| Apache | LimitRequestBody |
0/1 GiB (2.4.54+ defaults to 1 GiB) |
| Cloudflare proxy (if fronted) | request body cap | 100 MB on free plan |
Apache's default is usually a non-issue for podcast-sized files; nginx's 1M default bites almost immediately.
Prevention¶
- Put the drop-in under configuration management (Ansible role for the LAMP/podcast host group) so a rebuild reapplies it automatically.
- After any migration, grep the new host for stock PHP limits before declaring cutover complete:
- Record the override in the host's incident/config log so the next migration checklist includes it.
Related¶
- Patching PHP 8.4 Implicit-Nullable Deprecations in Vendor Packages
- Logwatch Reports Wrong Hostname After Migration — same class of post-migration config drift