# Reverse proxy for the audit request endpoint. # # Deliberately a separate server block on its own hostname, so it does not # touch the existing git / nextcloud vhosts on this machine. Reload nginx # rather than restarting it, and the other services never drop a connection. # # Install to /etc/nginx/sites-available/api.sascha-bach.de and symlink into # sites-enabled. certbot --nginx rewrites the TLS parts of this file. server { listen 80; listen [::]:80; server_name api.sascha-bach.de; # Needed once so certbot can answer the HTTP-01 challenge. location /.well-known/acme-challenge/ { root /var/www/html; } location / { return 301 https://$host$request_uri; } } # Rate-limits /deploy-webhook specifically. It is now reachable over the # public hostname (see the location block below), so unlike # /api/audit-request it has no per-request Node-side limiter of its own - # the HMAC signature check is the real gate, this is just a cheap first # filter against noise. limit_req_zone $binary_remote_addr zone=deploy_webhook:1m rate=10r/m; server { listen 443 ssl; listen [::]:443 ssl; http2 on; server_name api.sascha-bach.de; # certbot inserts ssl_certificate / ssl_certificate_key here. # The Node process caps bodies at 10 kB. Reject oversized ones at the edge # so they never reach it. client_max_body_size 16k; # Only these paths exist. Everything else is refused rather than # forwarded, which keeps the attack surface to what the form needs. location = /api/audit-request { proxy_pass http://127.0.0.1:3001; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; # Required: server.js runs with `trust proxy 1` and derives the # rate-limit key from this header. Without it every visitor shares # one bucket and three submissions lock out everyone for 15 minutes. proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 30s; } location = /health { proxy_pass http://127.0.0.1:3001; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; access_log off; } # Only needed if Gitea/Forgejo runs in a Docker container: 127.0.0.1 # inside that container is the container's own loopback, not this host, # so the webhook target can't be http://127.0.0.1:3002 in that case. # Routing it here instead works regardless of the container network # setup - the request simply goes out over HTTPS to this hostname and # back in, the same way any other webhook call would. # # If Gitea runs natively on this same machine (not in Docker), this # block is not needed - point the Gitea webhook straight at # http://127.0.0.1:3002/deploy-webhook instead and skip this location. location = /deploy-webhook { limit_req zone=deploy_webhook burst=5 nodelay; proxy_pass http://127.0.0.1:3002; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 30s; } location / { return 404; } }