Files
SkinbaseNova/docs/optimization-m13-x-accel-downloads.md
klevze e9cf754b37 Enable nginx X-Accel for original artwork downloads.
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.
2026-08-29 12:25:37 +02:00

3.4 KiB
Raw Permalink Blame History

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_ENABLED default false
  • Path: DOWNLOAD_ACCEL_PATH=/internal/originals
  • Laravel originals root: uploads.local_originals_root (symlink SkinbaseNova/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)

  1. 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)
  2. Insert the location inside the HTTPS server { } (before location /).
  3. sudo nginx -t — stop if it fails.
  4. sudo systemctl reload nginx — not restart.
  5. Direct access: curl -I https://skinbase.org/internal/originals/<relative-path> → 404.
  6. Optional: sudo -u www-data test -r <absolute-original> && echo READABLE

Stage B — Laravel flag

  1. Set DOWNLOAD_ACCEL_ENABLED=true (path already /internal/originals).
  2. sudo -u skinbase php artisan config:cache if config is cached.
  3. Verify with tinker as skinbase (flag true, path /internal/originals).
  4. Public: curl -fL https://skinbase.org/download/artwork/<ID> → 200, attachment, size matches original.
  5. Range: curl -H 'Range: bytes=0-99' → 206, Content-Range: bytes 0-99/<size>, 100-byte body.
  6. 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.