Skip to content

NGINX Configuration

The cloud-init provisioning deploys a fully performance-optimized NGINX CDN edge configuration. This page documents every configuration layer, from kernel tuning through cache behavior to vendor header injection. All settings were verified under a 48-hour continuous load test peaking at 172,540 req/s.

FilePurpose
/etc/sysctl.d/99-cdn-tuning.confLinux kernel network tuning
/etc/systemd/system/nginx.service.d/override.confFile descriptor limits for NGINX
/etc/security/limits.d/99-nginx.confOS-level limits for www-data user
/etc/nginx/nginx.confNGINX main config (workers, buffers, gzip, logging)
/etc/nginx/conf.d/cdn-edge.confCDN proxy config (cache, upstream, headers)

Applied via /etc/sysctl.d/99-cdn-tuning.conf at boot.

ParameterValueDefaultPurpose
net.core.somaxconn655354096Listen backlog queue size
net.core.netdev_max_backlog655351000Per-CPU incoming packet backlog
net.ipv4.tcp_max_syn_backlog65535256SYN request queue (prevents drops under burst)
net.ipv4.tcp_tw_reuse12Reuse TIME_WAIT sockets for outgoing connections
net.ipv4.ip_local_port_range1024-6553532768-60999Ephemeral port range (64K vs 28K)
net.core.rmem_max16 MB212 KBMaximum receive socket buffer
net.core.wmem_max16 MB212 KBMaximum send socket buffer
net.ipv4.tcp_rmem4K/87K/16M4K/131K/6MPer-socket receive buffer (min/default/max)
net.ipv4.tcp_wmem4K/65K/16M4K/16K/4MPer-socket send buffer (min/default/max)
net.ipv4.tcp_fin_timeout1560FIN_WAIT_2 timeout (faster socket cleanup)
net.ipv4.tcp_keepalive_time3007200Start keepalive probes after 5 min idle
net.ipv4.tcp_slow_start_after_idle01Keep congestion window warm on idle connections
net.ipv4.tcp_max_tw_buckets2000000~65536Maximum TIME_WAIT sockets
fs.file-max2097152variesSystem-wide file descriptor limit
vm.swappiness1060Prefer RAM over swap for cache data
worker_processes auto; # 4 workers on D4s_v5 (1 per vCPU)
worker_rlimit_nofile 65535; # Per-worker file descriptor limit
events {
use epoll; # Linux-optimized event model
worker_connections 8192; # 4 workers x 8192 = 32,768 max concurrent
multi_accept on; # Accept all pending connections per event loop
accept_mutex off; # Not needed with epoll + reuseport
}

Systemd override at /etc/systemd/system/nginx.service.d/override.conf sets LimitNOFILE=65535 to match.

proxy_buffering on;
proxy_buffer_size 16k; # Header buffer (handles large CDN headers)
proxy_buffers 64 16k; # 1 MB per connection (64 x 16k)
proxy_busy_buffers_size 256k; # Can send 256k to client while still reading

The 256k busy buffer size is critical — it must exceed the largest cached response (Juice Shop is 75 KB). The original 64k setting caused serialization under load.

gzip on;
gzip_comp_level 4; # Balance: ~85% of max compression at ~40% CPU
gzip_min_length 256; # Skip tiny responses (gzip header overhead)
gzip_vary on; # Vary: Accept-Encoding for correct caching
gzip_proxied any; # Compress all proxied responses
gzip_types text/plain text/css text/javascript text/xml
application/json application/javascript application/xml
application/xml+rss application/atom+xml
application/ld+json application/manifest+json
image/svg+xml;
open_file_cache max=200000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;

Caches file descriptors and metadata for cached objects, eliminating stat() and open() syscalls on hot files.

keepalive_timeout 65;
keepalive_requests 100000; # Was 1000 — caused connection recycling at 90K req/s

At 90K req/s with keepalive_requests 1000, connections recycled every 11 seconds, generating TIME_WAIT sockets. Increasing to 100,000 eliminated this entirely.

log_format cdn '$remote_addr [$time_local] "$request" $status $body_bytes_sent $upstream_cache_status $request_time';
access_log /var/log/nginx/access.log cdn buffer=256k flush=5s;

The cdn log format includes $upstream_cache_status (HIT/MISS) and $request_time for latency analysis. Buffered logging (256k/5s flush) reduces I/O overhead under high load.

upstream origin_backend {
server 20.12.78.159:80;
keepalive 256; # Persistent connections per worker to origin
keepalive_timeout 60s;
keepalive_requests 1000;
}

With 4 workers x 256 keepalive = 1,024 warm connections to the origin. Combined with proxy_http_version 1.1 and proxy_set_header Connection "", this eliminates TCP handshake overhead on every cache miss.

proxy_cache_path /var/cache/nginx/cdn
levels=1:2
keys_zone=cdn_cache:32m
max_size=25g
inactive=24h
use_temp_path=off;
ParameterValuePurpose
keys_zone=cdn_cache:32m32 MB shared memoryStores ~256,000 cache keys and metadata
max_size=25g25 GB disk limitUses most of the 30 GB OS disk; LRU eviction at limit
inactive=24h24-hour inactivity timeoutContent not accessed for 24h is evicted (not the same as TTL)
use_temp_path=offWrite directly to cache dirAvoids cross-filesystem file moves
proxy_cache cdn_cache;
proxy_cache_valid 200 301 302 4h;
proxy_cache_valid 404 1m;
proxy_cache_key "$scheme$host$request_uri";
proxy_cache_lock on;
proxy_cache_lock_age 3s;
proxy_cache_lock_timeout 3s;
proxy_cache_background_update on;
proxy_cache_use_stale updating error timeout http_500 http_502 http_503 http_504;
proxy_ignore_headers Set-Cookie Cache-Control Expires Vary;
proxy_hide_header X-Cache-Status;
proxy_hide_header Vary;
DirectiveValuePurpose
proxy_cache_valid 200 301 302 4h4-hour TTLCached content valid for 4 hours (increased to reduce refresh wave throughput dips)
proxy_cache_lock onThundering herd preventionOnly 1 request goes to origin per uncached URL; others wait for cache fill
proxy_cache_lock_age 3sLock timeoutIf first request hasn’t completed in 3s, allow another through
proxy_cache_background_update onZero-latency refreshServes stale content immediately while refreshing in background
proxy_cache_use_staleResilienceServes stale content during origin errors (500/502/503/504) or while updating
proxy_ignore_headersForce cachingIgnores origin Set-Cookie, Cache-Control, Expires, Vary — the CDN decides TTL and Vary behavior (Juice Shop sends max-age=0 and triple Vary headers which prevented effective caching)
proxy_hide_header X-Cache-StatusStrip origin headerOrigin NGINX adds its own X-Cache-Status — strip it so only the CDN’s cache status is visible
proxy_hide_header VaryPrevent cache fragmentationOrigin sends multiple Vary: Accept-Encoding headers. Stripping them prevents cache key fragmentation across Accept-Encoding permutations. NGINX’s gzip_vary on adds the correct single Vary header automatically
proxy_read_timeout 30s;
proxy_connect_timeout 10s;
proxy_send_timeout 15s;

Gives origin more response time under load while still failing fast on connection issues.

server {
listen 80 reuseport; # Kernel distributes connections across all 4 workers
server_name _;
}

reuseport enables SO_REUSEPORT — the kernel distributes incoming connections directly to worker processes, eliminating accept mutex contention.

The simulator injects headers from all five major CDN vendors simultaneously. This allows F5 XC to be configured with any vendor’s “Trusted Client IP Header” and see realistic header payloads regardless of which CDN is being simulated.

HeaderValuePurpose
X-Forwarded-ForClient IP chainStandard forwarded IP
X-Forwarded-Protohttp or httpsOriginal client protocol
X-Forwarded-HostOriginal hostnameOriginal Host header
X-Forwarded-PortServer portOriginal port
X-Real-IPClient IPSingle client IP (nginx convention)
Via1.1 cdn-simulatorProxy identification
ForwardedRFC 7239 formatStandardized forwarding header
CDN-Loopcdn-simulatorLoop detection
HeaderValuePurpose
True-Client-IPClient IPOriginal end-user IP address
X-Akamai-EdgescapeCompound geo stringgeoregion, country_code, region_code, city, dma, pmsa, msa, areacode, county, fips, lat, long, timezone, zip, continent, throughput, bw, network, asnum, network_type
X-Akamai-Device-CharacteristicsDevice propertiesbrand_name, model_name, is_mobile, is_tablet, is_wireless_device, device_os, device_os_version, resolution_width, resolution_height
X-Akamai-Request-IDRequest UUIDUnique request identifier
HeaderValuePurpose
CF-Connecting-IPClient IPTrue client IP (always present)
CF-IPCountryUSTwo-letter country code
cf-ipcitySan JoseClient city
cf-ipcontinentNAContinent code
cf-iplatitude / cf-iplongitudeCoordinatesGeolocation
cf-region / cf-region-codeCalifornia / CARegion info
cf-metro-code807US metro code
cf-postal-code95113Postal code
cf-timezoneAmerica/Los_AngelesIANA timezone
Cf-Ray{request_id}-SJCUnique ray ID with POP IATA code
CF-Visitor{"scheme":"https"}Visitor protocol info
cf-bot-score85Bot score (1=bot, 99=human)
cf-verified-botfalseKnown good bot flag
cf-ja3-hashe7d705a3286e19ea42f587b344ee6865JA3 TLS fingerprint
cf-ja4t13d1516h2_8daaf6152771_b0da82dd1658JA4 TLS fingerprint
HeaderValuePurpose
CloudFront-Viewer-AddressIP:portClient IP and source port
CloudFront-Viewer-CountryUSCountry code
CloudFront-Viewer-Country-NameUnited StatesFull country name
CloudFront-Viewer-Country-RegionCARegion code
CloudFront-Viewer-Country-Region-NameCaliforniaFull region name
CloudFront-Viewer-CitySan JoseClient city
CloudFront-Viewer-Postal-Code95113Postal code
CloudFront-Viewer-Latitude / Longitude37.33530 / -121.89300Geolocation
CloudFront-Viewer-Time-ZoneAmerica/Los_AngelesIANA timezone
CloudFront-Viewer-Metro-Code807US metro code
CloudFront-Viewer-ASN7018Autonomous System Number
CloudFront-Viewer-Http-Version2.0Client HTTP version
CloudFront-Forwarded-ProtohttpsOriginal protocol
CloudFront-Viewer-TLSTLSv1.3:TLS_AES_128_GCM_SHA256:sessionResumedTLS details
CloudFront-Viewer-JA3-Fingerprinte7d705a3286e19ea42f587b344ee6865JA3 TLS fingerprint
CloudFront-Is-Desktop-Viewertrue/falseDevice detection
CloudFront-Is-Mobile-Viewertrue/falseDevice detection
CloudFront-Is-Tablet-Viewertrue/falseDevice detection
CloudFront-Is-SmartTV-ViewerfalseDevice detection
X-Amz-Cf-IdEncoded IDCloudFront request identifier
HeaderValuePurpose
Fastly-Client-IPClient IPTrue client IP
Fastly-SSL1Connection was over TLS
Fastly-Client1Client-facing request (not shield)
Fastly-FFcache-sjc3120-SJCCache node identification
X-Geo-Country-CodeUSCountry (VCL variable convention)
X-Geo-Country-Code3USAThree-letter country code
X-Geo-Country-NameUnited StatesFull country name
X-Geo-CitySan JoseClient city
X-Geo-RegionCARegion code
X-Geo-Continent-CodeNAContinent
X-Geo-Latitude / X-Geo-Longitude37.3353 / -121.8938Geolocation
X-Geo-Postal-Code95113Postal code
X-Geo-Metro-Code807US metro code
X-Geo-ASN7018Autonomous System Number
X-Geo-Conn-SpeedbroadbandConnection speed class
X-Geo-Conn-TypewiredConnection type
HeaderValuePurpose
X-Azure-ClientIPClient IPClient IP address
X-Azure-SocketIPClient IPTCP socket source IP
X-Azure-RefEncoded ref stringUnique request reference for troubleshooting
X-Azure-FDIDa0a0a0a0-bbbb-cccc-dddd-e1e1e1e1e1e1Front Door resource identifier
X-Azure-RequestChainhops=1Loop detection hop count

Response Headers (Added to Client Responses)

Section titled “Response Headers (Added to Client Responses)”
HeaderValuesPurpose
X-Cache-StatusHIT, MISS, EXPIRED, STALE, UPDATINGCache behavior for this request
X-CDN-Edgecdn-simulatorIdentifies this edge node
X-CDN-POPSJCSimulated Point of Presence IATA code
X-Served-Bycache-sjc3120-SJCSimulated cache node in Fastly format
X-Request-IDUUID (per-request)Unique request identifier

The simulator detects device type from the User-Agent header using NGINX map directives:

  • Mobile: Matches iPhone, Android (non-tablet), iPod, BlackBerry, Opera Mini, IEMobile
  • Tablet: Matches iPad, Android tablet, Kindle, PlayBook
  • Desktop: Default when neither mobile nor tablet matches

Device type is reflected in:

  • CloudFront-Is-Desktop-Viewer / CloudFront-Is-Mobile-Viewer / CloudFront-Is-Tablet-Viewer
  • X-Akamai-Device-Characteristics (is_mobile, is_tablet, is_wireless_device fields)
Terminal window
ssh azureuser@<PUBLIC_IP>
# Update upstream server
sudo sed -i 's|server .*;|server NEW_HOST:80;|' /etc/nginx/conf.d/cdn-edge.conf
# Clear cache and reload
sudo rm -rf /var/cache/nginx/cdn/*
sudo nginx -t && sudo systemctl reload nginx

Or update origin_host in terraform.tfvars and run terraform apply to reprovision.

Terminal window
ssh azureuser@<PUBLIC_IP>
sudo rm -rf /var/cache/nginx/cdn/*
sudo systemctl reload nginx
Terminal window
# Object count and disk usage
sudo find /var/cache/nginx/cdn -type f | wc -l
sudo du -sh /var/cache/nginx/cdn
# Recent access log with cache status
tail -20 /var/log/nginx/access.log
Terminal window
# Real-time connections and socket states
ss -s
# NGINX worker CPU usage
top -bn1 | grep nginx
# Upstream keepalive connections
ss -tn state established dst <ORIGIN_IP> | wc -l
# TIME_WAIT socket count
ss -tn state time-wait | wc -l

Verified under 48-hour continuous load test:

MetricValue
Peak throughput (cached)172,540 req/s
Sustained throughput (cached)85,000-103,000 req/s
Peak connections15,000 concurrent
Cache hit ratio100% (when warmed)
Memory under load1.2 GB stable (8% of 16 GB)
CPU at peak100% (4 cores - CPU is the ceiling)
Errors during 48h test0
Memory leaksNone detected
Connection leaksNone detected