Skip to content

Castopod "Max size is 2MiB" — Stock PHP Upload Limits Resurface After a Server Rebuild

TL;DR — Debian/Ubuntu ship PHP with upload_max_filesize = 2M and post_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 editing php.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_filesize and post_max_size values 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:

  1. 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.
  2. The tuning was config drift, not managed state. The original server had the limits raised at some point — by hand, in php.ini or 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:

2M / 8M / 128M

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_size must be ≥ upload_max_filesize (the whole request body has to fit).
  • memory_limit does 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 = 300 covers 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:
    php -i | grep -E 'upload_max_filesize|post_max_size'   # then repeat via FPM socket
    
  • Record the override in the host's incident/config log so the next migration checklist includes it.