Skip to content

Server

Software Version
PHP 8.5 or newer (CLI and FPM/module)
CodeIgniter 4.7 or newer
MySQL 8.0 or newer, 8.4 LTS recommended
Web server any; the document root must be public/

PHP extensions:

Extension Needed for
intl, mbstring, json, sodium Anvil itself (key ring, MFA secrets, API secrets)
mysqli CodeIgniter’s MySQLi database driver
fileinfo content type detection of uploads
curl single sign-on with OpenID Connect (CodeIgniter’s CURLRequest)
openssl, dom, libxml SAML (onelogin/php-saml)
zip, xmlreader exports to XLSX (OpenSpout)

Composer checks most of them; run the check on the target server with the locked versions:

Terminal window
composer check-platform-reqs --no-dev
php spark phpini:check

phpini:check compares php.ini with recommended production values (php spark phpini:check opcache for OPcache). Make sure post_max_size and upload_max_filesize allow Config\Anvil::$fileMaxSize (10 MB by default; one upload can carry up to 10 files).

  • The document root is public/. app/, vendor/, writable/ and .env must not be reachable over HTTP.
  • writable/ is the only directory the PHP process writes to at runtime: cache/, logs/, session/ (file sessions), anvil/files/ (uploads), anvil/exports/ (export files). Give it to the user PHP runs as and keep the application code read-only for that user.
  • public/assets/anvil/ (Config\Anvil::$assetsPath) is written by anvil:publish during the deployment, not at runtime.
  • Uploads and exports can live elsewhere (Config\Anvil::$filePath, $exportPath), but always outside the document root.

Point the document root to public/ and send every request that is not a file to index.php.

server {
listen 443 ssl;
http2 on;
server_name admin.example.com;
root /var/www/app/public;
index index.php;
client_max_body_size 12m; # at least Config\Anvil::$fileMaxSize
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
fastcgi_pass unix:/run/php/php8.5-fpm.sock;
}
# Assets carry a version (?v=<hash>), so they can be cached long.
location /assets/anvil/ {
expires 1y;
add_header Cache-Control "public, immutable" always;
}
location ~ /\. { deny all; }
}

$realpath_root makes PHP-FPM follow the current symlink of a new release, see Releases.

<VirtualHost *:443>
ServerName admin.example.com
DocumentRoot /var/www/app/public
<Directory /var/www/app/public>
AllowOverride All # the .htaccess of CodeIgniter
Require all granted
</Directory>
</VirtualHost>

CodeIgniter’s public/.htaccess rewrites to index.php (mod_rewrite).

Setting Value
upload_max_filesize, post_max_size at least $fileMaxSize; post_max_size a little larger (form fields)
memory_limit 256M; exports with many rows run in the queue worker
max_execution_time 30–60 s for web requests
expose_php Off
session.use_strict_mode 1
opcache.enable 1
opcache.validate_timestamps 0 in production; reload PHP-FPM after every release

php spark phpini:check and php spark phpini:check opcache compare the current values with CodeIgniter’s recommendations.