# ────────────────────────────────────────────────────────────────── # Ulicraft host nginx — TLS terminator in FRONT of caddy. # ────────────────────────────────────────────────────────────────── # caddy (in docker-compose.yml) is the single internal ingress for ALL vhosts; # this host nginx terminates TLS and reverse-proxies everything to caddy by Host # header. Prefer tooling/render-nginx.sh over hand-rendering. # # Caddy must be published on a localhost port — set in .env: # CADDY_HTTP_PORT=8880 # CADDY_HTTP_BIND=127.0.0.1 # and the stack brought up: # docker compose up -d # # Render with the vars then install: # CADDY_HTTP_PORT=8880 BASE_DOMAIN=ulicraft.net APP_DIR=/home/ubuntu/mc/ulicraft-server-v1 \ # envsubst '$CADDY_HTTP_PORT $BASE_DOMAIN $APP_DIR' \ # < nginx/ulicraft-caddy.conf.tmpl | sudo tee /etc/nginx/sites-available/ulicraft.conf # sudo ln -sf /etc/nginx/sites-available/ulicraft.conf /etc/nginx/sites-enabled/ # sudo nginx -t && sudo systemctl reload nginx # # Prereqs: # - certs issued: tooling/issue-letsencrypt.sh -> $APP_DIR/certs//{cert,key}.pem # ────────────────────────────────────────────────────────────────── upstream ulicraft_caddy { server 127.0.0.1:${CADDY_HTTP_PORT}; } # Brute-force throttle for the publicly-exposed Filestash /admin console (it is # the only credential on files.), applied in its location block below. limit_req_zone $binary_remote_addr zone=files_admin:10m rate=6r/m; # HTTP -> HTTPS for every name. server { listen 80; listen [::]:80; server_name ${BASE_DOMAIN} www.${BASE_DOMAIN} auth.${BASE_DOMAIN} distribution.${BASE_DOMAIN} avatar.${BASE_DOMAIN} status.${BASE_DOMAIN} files.${BASE_DOMAIN}; return 301 https://$host$request_uri; } # One TLS server block per name (each has its own LE cert). All proxy to caddy; # caddy routes by the forwarded Host header (apex -> www, auth -> drasl). server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name ${BASE_DOMAIN}; ssl_certificate ${APP_DIR}/certs/${BASE_DOMAIN}/cert.pem; ssl_certificate_key ${APP_DIR}/certs/${BASE_DOMAIN}/key.pem; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; client_max_body_size 4m; # skin uploads (auth) reuse the same proxy block location / { proxy_pass http://ulicraft_caddy; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } # www -> apex canonical redirect (needs its own cert; covered by LE_SUBDOMAINS). server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name www.${BASE_DOMAIN}; ssl_certificate ${APP_DIR}/certs/www.${BASE_DOMAIN}/cert.pem; ssl_certificate_key ${APP_DIR}/certs/www.${BASE_DOMAIN}/key.pem; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; return 301 https://${BASE_DOMAIN}$request_uri; } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name auth.${BASE_DOMAIN}; ssl_certificate ${APP_DIR}/certs/auth.${BASE_DOMAIN}/cert.pem; ssl_certificate_key ${APP_DIR}/certs/auth.${BASE_DOMAIN}/key.pem; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; client_max_body_size 4m; # skin uploads location / { proxy_pass http://ulicraft_caddy; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name distribution.${BASE_DOMAIN}; ssl_certificate ${APP_DIR}/certs/distribution.${BASE_DOMAIN}/cert.pem; ssl_certificate_key ${APP_DIR}/certs/distribution.${BASE_DOMAIN}/key.pem; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; location / { proxy_pass http://ulicraft_caddy; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name avatar.${BASE_DOMAIN}; ssl_certificate ${APP_DIR}/certs/avatar.${BASE_DOMAIN}/cert.pem; ssl_certificate_key ${APP_DIR}/certs/avatar.${BASE_DOMAIN}/key.pem; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; location / { proxy_pass http://ulicraft_caddy; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } # Filestash player file share. Uploads need a real body limit (nginx defaults to # 1m, which rejects almost anything worth sharing) and streaming rather than # spooling — a 2g upload buffered to disk would write it twice on a host whose # disk is already the shared failure domain for Minecraft + Drasl + backups. server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name files.${BASE_DOMAIN}; ssl_certificate ${APP_DIR}/certs/files.${BASE_DOMAIN}/cert.pem; ssl_certificate_key ${APP_DIR}/certs/files.${BASE_DOMAIN}/key.pem; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; client_max_body_size 2g; # uploads; other vhosts stay at 4m proxy_request_buffering off; # stream uploads, don't spool them to disk proxy_read_timeout 300s; proxy_send_timeout 300s; # /admin is reachable on purpose, so throttle it: it is the only password # on this vhost. 6 req/min, small burst. location /admin { limit_req zone=files_admin burst=3 nodelay; proxy_pass http://ulicraft_caddy; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } location / { proxy_pass http://ulicraft_caddy; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name status.${BASE_DOMAIN}; ssl_certificate ${APP_DIR}/certs/status.${BASE_DOMAIN}/cert.pem; ssl_certificate_key ${APP_DIR}/certs/status.${BASE_DOMAIN}/key.pem; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; location / { proxy_pass http://ulicraft_caddy; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # Uptime Kuma uses websockets (socket.io) for live status updates. proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } }