Skip to content
Products
Resources
Solutions
Pricing
Contact
Log In
Sign Up
Docs
Getting Started
License
Concepts
Architecture
Requirements
Server configuration
Installation
Deployment
Releases
Upgrades
NEO
Configuration
Apps
CODE
How to Add an App
Auth
Docs
Getting Started
Server configuration
On this page

Server configuration

Tune the web server and PHP for UNA 15 — rewrites, compression, static cache headers, OPcache, and container traps.
Core
Base
~15.0
Source
Copy Page
Note
Important
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5;
gzip_types
    text/plain text/css text/xml text/javascript
    application/javascript application/json application/xml
    application/rss+xml image/svg+xml;
# Prefer not to gzip binaries (woff2/jpeg/png/webp) — omit them from gzip_types.
# Avoid re-gzipping URI gzip_loader.php (already encoded by BxDolGzip).
# Enable: a2enmod deflate
<IfModule mod_deflate.c>
    AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css
    AddOutputFilterByType DEFLATE text/javascript application/javascript application/json
    AddOutputFilterByType DEFLATE application/xml image/svg+xml
    # Skip already-compressed formats
    SetEnvIfNoCase Request_URI \\.(?
### Caddy

`scripts/Caddyfile` already has `encode zstd br gzip` and does **not** exclude fonts, images, or `gzip_loader.php`. Keep that sample as-is. If another proxy in front also compresses, watch for double-gzip on loader responses.

## Static asset caching

Long browser cache on **real** static files (templates, module assets, `cache_public/`, fonts, images on disk) is intended. Naïve “anything ending in `.css` / `.js` / `.jpg` is static” rules break UNA:

- Local storage URLs look like `/s/{object}/{file.ext}` and must hit PHP (`storage.php`).
- Missing SEO paths fall through to `r.php` and can also end in asset-like extensions.

**Shipped nginx** (`scripts/docker-compose/nginx.conf`) rewrites `/s/`, `/page/`, `/m/`, then uses `if (!-e $request_filename)` to send missing paths to `r.php`. It does **not** use `try_files`, and it sets **no** `gzip`, `expires`, or static `Cache-Control`. **Caddy** rewrites `/s/` then uses `try_files`. Root `.htaccess` serves existing files, then rewrites the rest to `r.php`. None of those samples set long cache headers on static files.

> [!IMPORTANT]
> When **adding** static cache rules, only attach long Cache-Control / Expires after an existence check proves the file is on disk. Never short-circuit `/s/`, `/page/`, `/m/`, or the `r.php` fallback with an extension-only static location.

### nginx (recommendation when adding cache headers)

```nginx
# After UNA /s/ rewrite and if (!-e …) → r.php rules (see scripts/docker-compose/nginx.conf).
# try_files here is an operator pattern for static expires — not what the shipped sample uses for routing.
location ~* \\.(?:css|js|mjs|map|jpg|jpeg|png|gif|webp|svg|ico|woff2?|ttf|eot)$ {
    # Only if the file exists; otherwise fall through to PHP / r.php
    try_files $uri =404;
    expires 30d;
Keep `/s/…jpg` on the shipped rewrite to `storage.php`; do not let this location win over that rewrite.

### Apache `mod_expires` (recommendation)

```apache
# Enable: a2enmod expires
<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType text/css "access plus 30 days"
    ExpiresByType application/javascript "access plus 30 days"
    ExpiresByType image/jpeg "access plus 30 days"
    ExpiresByType image/png "access plus 30 days"
    ExpiresByType image/webp "access plus 30 days"
    ExpiresByType image/svg+xml "access plus 30 days"
    ExpiresByType font/woff2 "access plus 30 days"
</IfModule>
# Alongside encode and UNA rewrites in scripts/Caddyfile.
@static {
    file
    path *.css *.js *.mjs *.map *.jpg *.jpeg *.png *.gif *.webp *.svg *.ico *.woff *.woff2
    not path /s/* /page/* /m/*
}
header @static Cache-Control "publ
## Apache, nginx, Caddy, and Railway

| Stack | Shipped in repo | Operator add-ons (recommendation) |
| --- | --- | --- |
| Apache / Railway | `a2enmod rewrite` only | `deflate` and `expires` (Studio audit looks for them) |
| nginx sample | Rewrites + `if (!-e …)` → `r.php`; no gzip / expires | Add `gzip` + existence-aware static expires if you want them |
| Caddy / FrankenPHP sample | `encode zstd br gzip` (no exclusions) + rewrites + `try_files` | Optional matcher-scoped `Cache-Control` as above |
| Railway `railway.toml` | Build + restart policy only — **no cron** | Add a separate cron process that runs `periodic/cron.php` every minute |

Ubuntu zip install docs match Railway on modules: rewrite only. For custom images, enable extra Apache modules in the Dockerfile or entrypoint the same way rewrite is enabled today.

Reference paths: `scripts/docker-compose/nginx.conf`, `scripts/Caddyfile`, `scripts/railway/Dockerfile`, `scripts/railway/entrypoint.sh`, `scripts/railway/railway.toml`, root `.htaccess`, `gzip_loader.php`.

## PHP OPcache

OPcache is optional on the [Requirements](wiki/requirements-overview) checklist but strongly recommended in production. File cache under `cache/` is often `include()`d PHP (`BxDolCacheFile`) with **hash-named** files (site hash in the name). Writers call `opcache_invalidate` when those files change. Undersized OPcache still thrash under many unique files — size it for large multi-module sites.

**Shipped docker-compose** (`scripts/docker-compose/php.ini` and `php-cron.ini`):

| Directive | Packaged value |
| --- | --- |
| `opcache.enable` | `1` |
| `opcache.memory_consumption` | `72` |
| `opcache.interned_strings_buffer` 
**Production guidance:** raise `opcache.memory_consumption` and `opcache.interned_strings_buffer` **above the docker sample 72 / 16** on busy sites. Use `opcache_get_status()` to watch memory pressure; do not treat any particular larger pair as a UNA-shipped floor.

```ini
opcache.enable=1
opcache.memory_consumption=72
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=100000
opcache.validate_timestamps=1
opcache.revalidate_freq=0

Keep `/s/`, `/page/`, and `/m/` on the PHP rewrite path; apply `Cache-Control` only when `file` matches a real asset.

    add_header Cache-Control "public, max-age=2592000";
    access_log off;
}
3d
On this page
Web server and rewritePublic URL reachability from PHPSite URL, HTTPS, and proxiesCompressionnginx (recommendation)Apache mod_deflate (recommendation)Apache mod_expires (recommendation)Caddy (recommendation when adding Cache-Control)
Auto