The edge had no limit_req/limit_conn at all, so a single client could flood the Next.js backend and the Nitro client with unbounded parallel requests. Traefik's logs already showed this: bursts of gamedata icon requests answered with 429. Add limit_req (30r/s, burst 60, nodelay) and limit_conn (30) zones keyed on the real client IP, applied at server scope so both cached assets and proxied API routes share one budget. The burst is deliberately generous because the Nitro client fetches gamedata and icons in bursts when loading a room.
66 lines
2.5 KiB
Nginx Configuration File
66 lines
2.5 KiB
Nginx Configuration File
# Canonical nginx config for the EpicNabbo CMS edge.
|
|
# Source of truth: repository deployment/proxy/nginx-cms.conf (the site block)
|
|
# and this file. Installed/synced by scripts/nginx-sync.sh so it cannot be
|
|
# lost again while nginx keeps running on an in-memory copy.
|
|
#
|
|
# Traffic path: Cloudflare -> Traefik (:443) -> nginx (:9443) -> CMS (:3002),
|
|
# with direct Cloudflare-origin and game-client (ws.epicnabbo.nl) connections
|
|
# also terminating on :9443.
|
|
# nginx is the last layer that can still rewrite Cache-Control, so it owns the
|
|
# headers it adds explicitly; everything proxied to the CMS is passed through
|
|
# untouched unless this file says otherwise.
|
|
|
|
user www-data;
|
|
worker_processes auto;
|
|
# Raise the file-descriptor rlimit for the workers. Must stay <= the master's
|
|
# RLIMIT_NOFILE *hard* limit, otherwise nginx refuses to start with
|
|
# "setrlimit(RLIMIT_NOFILE) failed". Bounded from above by the systemd drop-in
|
|
# /etc/systemd/system/nginx.service.d/override.conf (LimitNOFILE=65536).
|
|
worker_rlimit_nofile 65536;
|
|
pid /run/nginx.pid;
|
|
|
|
error_log /var/log/nginx/error.log warn;
|
|
|
|
events {
|
|
worker_connections 2048;
|
|
use epoll;
|
|
}
|
|
|
|
http {
|
|
include /etc/nginx/mime.types;
|
|
default_type application/octet-stream;
|
|
|
|
# Compression is done once, at the edge (Traefik / Cloudflare). Enabling
|
|
# gzip here too would double-compress proxied responses and fight Vary.
|
|
gzip off;
|
|
|
|
sendfile on;
|
|
tcp_nopush on;
|
|
server_tokens off;
|
|
keepalive_timeout 30s;
|
|
|
|
client_max_body_size 64m;
|
|
client_body_buffer_size 16k;
|
|
client_header_buffer_size 1k;
|
|
large_client_header_buffers 4 8k;
|
|
|
|
# Rate limiting per client IP. The Nitro client fetches gamedata and icons in
|
|
# bursts when booting a room, so the burst is deliberately generous: it caps
|
|
# sustained floods without punishing a normal room load.
|
|
limit_req_zone $binary_remote_addr zone=cms_req_per_ip:10m rate=30r/s;
|
|
limit_conn_zone $binary_remote_addr zone=cms_conn_per_ip:10m;
|
|
|
|
# Blue/green cutover: ci-deploy.sh writes the active upstream here, and
|
|
# `proxy_pass http://cms_app` below follows it via graceful nginx -s reload.
|
|
upstream cms_app {
|
|
include /etc/nginx/snippets/cms_upstream_servers.conf;
|
|
}
|
|
|
|
# Cache policy maps and server blocks live in the site file so they are
|
|
# synced together and can never drift apart.
|
|
include /etc/nginx/sites-enabled/*.conf;
|
|
|
|
# Trusted edge / real-IP handling (regenerated by scripts/cf-ips-sync.sh
|
|
# from the live Cloudflare ranges; installed via scripts/nginx-sync.sh).
|
|
include /etc/nginx/conf.d/cloudflare-ips.conf;
|
|
} |