Hand originals to nginx after auth so PHP is not in the byte path. Keep DOWNLOAD_ACCEL_ENABLED off until the internal location is verified.
3.4 KiB
M13 — Artwork Download X-Accel-Redirect Offload
Application tests and nginx snippet only. Do not enable in production until Stage A nginx is verified.
Pre-M13 evidence
/download/artwork/{id} (M12 analyzer, skinbase.org):
| count | 2363 |
| p50 / p95 / p99 / max | 0.535 / 1.526 / 3.335 / 16.037 s |
| avg request_time | 0.7175 s |
| avg upstream_response_time | 0.3762 s |
| avg bytes | ~528 KB |
Slow requests show PHP finishing in ~0.3–0.5 s while request_time stays large: FPM is stuck sending the body.
After X-Accel: upstream_response_time is still Laravel work; request_time can still include slow clients. The win is freeing PHP-FPM, not always a tiny nginx request_time.
Existing application
- Route:
GET /download/artwork/{id}→ArtworkDownloadController - Flag:
DOWNLOAD_ACCEL_ENABLEDdefault false - Path:
DOWNLOAD_ACCEL_PATH=/internal/originals - Laravel originals root:
uploads.local_originals_root(symlinkSkinbaseNova/storage→ shared) - Fallback:
response()->download()— must stay
Controller already authorizes, checks file, records analytics, then optionally sets X-Accel-Redirect.
nginx mapping
Production vhost is not in this repo: /etc/nginx/sites-enabled/skinbase.org.conf.
Snippet: deploy/nginx/download-accel.conf
location ^~ /internal/originals/ {
internal;
alias /opt/www/virtual/SkinbaseNova.releases/shared/storage/app/originals/artworks/;
}
Alias uses the canonical shared tree so a release switch cannot point nginx at a missing release directory. Laravel still uses local_originals_root; the X-Accel URI is only the relative hash path (/27/21/….zip).
internal; is mandatory: a browser GET to /internal/originals/... must 404.
No sendfile/tcp/buffering changes in M13.
Rollout (operator — not executed here)
Stage A — nginx only (DOWNLOAD_ACCEL_ENABLED=false)
- Backup:
sudo cp /etc/nginx/sites-enabled/skinbase.org.conf /etc/nginx/sites-enabled/skinbase.org.conf.bak-$(date +%Y%m%d-%H%M%S) - Insert the location inside the HTTPS
server { }(beforelocation /). sudo nginx -t— stop if it fails.sudo systemctl reload nginx— not restart.- Direct access:
curl -I https://skinbase.org/internal/originals/<relative-path>→ 404. - Optional:
sudo -u www-data test -r <absolute-original> && echo READABLE
Stage B — Laravel flag
- Set
DOWNLOAD_ACCEL_ENABLED=true(path already/internal/originals). sudo -u skinbase php artisan config:cacheif config is cached.- Verify with tinker as
skinbase(flag true, path/internal/originals). - Public:
curl -fL https://skinbase.org/download/artwork/<ID>→ 200, attachment, size matches original. - Range:
curl -H 'Range: bytes=0-99'→ 206,Content-Range: bytes 0-99/<size>, 100-byte body. - Repeat direct internal URL → still 404.
Application deploy: bash ./sync.sh. Never git pull on production.
Rollback
First: DOWNLOAD_ACCEL_ENABLED=false then sudo -u skinbase php artisan config:cache if needed. PHP fallback returns immediately. Nginx location can stay.
If nginx must be reverted: restore the .bak-* vhost, nginx -t, reload.
Analyzer
Keep M12 skinbase-performance.log. After activation, upstream_response_time should stay near Laravel work; do not expect request_time to collapse for slow clients.