# nginx tile cache for Martin vector tile server # Proxy-cache layer: 10-100x faster on repeat requests, zero PostGIS load for cached tiles proxy_cache_path /var/cache/nginx/tiles levels=1:2 keys_zone=tiles:64m max_size=2g inactive=7d use_temp_path=off; # Log format with cache status for monitoring (docker logs tile-cache | grep HIT/MISS) log_format tiles '$remote_addr [$time_local] "$request" $status ' 'cache=$upstream_cache_status size=$body_bytes_sent ' 'time=$request_time'; server { access_log /var/log/nginx/access.log tiles; listen 80; server_name _; # Health check location = /health { access_log off; return 200 "ok\n"; add_header Content-Type text/plain; } # nginx status (active connections, request counts) — for monitoring location = /status { access_log off; stub_status on; } # Martin catalog endpoint (no cache) location = /catalog { proxy_pass http://martin:3000/catalog; proxy_set_header Host $host; } # PMTiles from MinIO — HTTPS proxy for browser access (avoids mixed-content block) # Browser fetches: /pmtiles/overview.pmtiles → MinIO http://10.10.10.166:9002/tiles/overview.pmtiles location /pmtiles/ { proxy_pass http://10.10.10.166:9002/tiles/; proxy_set_header Host 10.10.10.166:9002; proxy_http_version 1.1; # Range requests — essential for PMTiles (byte-range tile lookups) proxy_set_header Range $http_range; proxy_set_header If-Range $http_if_range; proxy_pass_request_headers on; # Browser cache — file changes only on rebuild (~weekly) add_header Cache-Control "public, max-age=3600, stale-while-revalidate=86400" always; # CORS — PMTiles loaded from tools.beletage.ro page add_header Access-Control-Allow-Origin "*" always; add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS" always; add_header Access-Control-Allow-Headers "Range, If-None-Match, If-Range, Accept-Encoding" always; add_header Access-Control-Expose-Headers "Content-Range, Content-Length, ETag, Accept-Ranges" always; # Preflight if ($request_method = OPTIONS) { add_header Access-Control-Allow-Origin "*"; add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS"; add_header Access-Control-Allow-Headers "Range, If-Range"; add_header Access-Control-Max-Age 86400; add_header Content-Length 0; return 204; } } # Tile requests — cache aggressively location / { proxy_pass http://martin:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; # Cache config — tiles change only on sync (weekly), long TTL is safe proxy_cache tiles; proxy_cache_key "$request_uri"; proxy_cache_valid 200 7d; proxy_cache_valid 204 1h; proxy_cache_valid 404 1m; proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; proxy_cache_lock on; proxy_cache_lock_timeout 5s; # Browser caching — tiles are immutable between syncs add_header Cache-Control "public, max-age=86400, stale-while-revalidate=604800" always; # Pass cache status header (useful for debugging) add_header X-Cache-Status $upstream_cache_status always; # CORS headers for tile requests add_header Access-Control-Allow-Origin "*" always; add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS" always; add_header Access-Control-Allow-Headers "Range, If-None-Match, Accept-Encoding" always; add_header Access-Control-Expose-Headers "Content-Range, Content-Length, ETag, X-Cache-Status" always; # Handle preflight if ($request_method = OPTIONS) { add_header Access-Control-Allow-Origin "*"; add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS"; add_header Access-Control-Max-Age 86400; add_header Content-Length 0; return 204; } # Let Martin gzip natively — pass compressed response through to client and cache gzip off; # Timeouts (Martin can be slow on low-zoom tiles) proxy_connect_timeout 10s; proxy_read_timeout 120s; proxy_send_timeout 30s; } }