1- # FieldTrack 2.0 — Nginx Reverse Proxy Configuration
2- #
1+ # ============================================================================
2+ # FieldTrack 2.0 — Nginx Reverse Proxy
33# Domain: fieldtrack.meowsician.tech
4- #
5- # SETUP:
6- # 1. Symlink to /etc/nginx/sites-enabled/fieldtrack
7- # 2. Run: sudo certbot --nginx -d fieldtrack.meowsician.tech
8- # 3. Test: sudo nginx -t && sudo systemctl reload nginx
9- #
10- # DEPLOYMENT:
11- # The deploy-bluegreen.sh script swaps the upstream server definition
12- # between 127.0.0.1:3001 and 127.0.0.1:3002 using sed.
13- # Only ONE line needs to change — the "server" directive in the upstream block.
14- #
15- # PUBLIC ROUTES:
16- # / → root status JSON
17- # /health → backend health check
18- # /api/* → backend API (prefix stripped)
19- # /monitor → Grafana dashboard
20- #
21- # BLOCKED (403):
22- # /metrics, /internal/, /prometheus, /node-exporter
23-
24- # ── Backend upstream ──────────────────────────────────────────────────────────
25- # Single source of truth for the active backend container.
26- # deploy-bluegreen.sh changes ONLY this line during deployment.
4+ # ============================================================================
5+
6+
7+ # ─────────────────────────────────────────────────────────────────────────────
8+ # Backend upstream (blue/green deployment target)
9+ # deploy-bluegreen.sh switches this port between 3001 and 3002
10+ # ─────────────────────────────────────────────────────────────────────────────
2711upstream fieldtrack_backend {
2812 server 127.0.0.1:3001;
13+ keepalive 32;
2914}
3015
31- # ── Rate limiting zones ────────────────────────────────────────────────────────
16+
17+ # ─────────────────────────────────────────────────────────────────────────────
18+ # Rate limiting
19+ # ─────────────────────────────────────────────────────────────────────────────
3220limit_req_zone $binary_remote_addr zone=fieldtrack_api:10m rate=30r/s;
3321limit_req_zone $binary_remote_addr zone=fieldtrack_health:10m rate=5r/s;
3422
35- # ── HTTP → HTTPS redirect ─────────────────────────────────────────────────────
23+
24+ # ─────────────────────────────────────────────────────────────────────────────
25+ # HTTP → HTTPS redirect
26+ # ─────────────────────────────────────────────────────────────────────────────
3627server {
28+
3729 listen 80;
3830 listen [::]:80;
31+
3932 server_name fieldtrack.meowsician.tech;
4033
41- # Allow ACME challenge for Certbot renewals
4234 location /.well-known/acme-challenge/ {
4335 root /var/www/certbot;
44- allow all;
4536 }
4637
4738 location / {
4839 return 301 https://$host$request_uri;
4940 }
5041}
5142
52- # ── HTTPS server ───────────────────────────────────────────────────────────────
43+
44+ # ─────────────────────────────────────────────────────────────────────────────
45+ # HTTPS server
46+ # ─────────────────────────────────────────────────────────────────────────────
5347server {
48+
5449 listen 443 ssl http2;
5550 listen [::]:443 ssl http2;
51+
5652 server_name fieldtrack.meowsician.tech;
5753
58- # ── SSL (Certbot will manage these after first run) ────────────────────────
59- # ssl_certificate /etc/letsencrypt/live/fieldtrack.meowsician.tech/fullchain.pem;
60- # ssl_certificate_key /etc/letsencrypt/live/fieldtrack.meowsician.tech/privkey.pem;
61- # include /etc/letsencrypt/options-ssl-nginx.conf;
62- # ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
63-
64- # ── Security headers ───────────────────────────────────────────────────────
65- add_header X-Frame-Options "SAMEORIGIN" always;
66- add_header X-Content-Type-Options "nosniff" always;
67- add_header X-XSS-Protection "1; mode=block" always;
68- add_header Referrer-Policy "strict-origin-when-cross-origin" always;
54+
55+ # ── SSL (managed by Certbot) ─────────────────────────────────────────────
56+ ssl_certificate /etc/letsencrypt/live/fieldtrack.meowsician.tech/fullchain.pem;
57+ ssl_certificate_key /etc/letsencrypt/live/fieldtrack.meowsician.tech/privkey.pem;
58+ include /etc/letsencrypt/options-ssl-nginx.conf;
59+ ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
60+
61+
62+ # ── Real client IP ───────────────────────────────────────────────────────
63+ real_ip_header X-Forwarded-For;
64+ set_real_ip_from 127.0.0.1;
65+ real_ip_recursive on;
66+
67+
68+ # ── Security headers ─────────────────────────────────────────────────────
69+ add_header X-Frame-Options "SAMEORIGIN" always;
70+ add_header X-Content-Type-Options "nosniff" always;
71+ add_header X-XSS-Protection "1; mode=block" always;
72+ add_header Referrer-Policy "strict-origin-when-cross-origin" always;
6973 add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
7074
71- # ── Logging ────────────────────────────────────────────────────────────────
75+
76+ # ── Logging ──────────────────────────────────────────────────────────────
7277 access_log /var/log/nginx/fieldtrack_access.log;
7378 error_log /var/log/nginx/fieldtrack_error.log;
7479
75- # ── Client limits ─────────────────────────────────────────────────────────
80+
81+ # ── Limits ───────────────────────────────────────────────────────────────
7682 client_max_body_size 10M;
7783
78- # ── BLOCKED: Internal/monitoring endpoints (return 403) ─────────────────────
84+
85+ # ─────────────────────────────────────────────────────────────────────────
86+ # BLOCKED endpoints
87+ # ─────────────────────────────────────────────────────────────────────────
7988 location /metrics {
8089 return 403;
8190 }
@@ -92,81 +101,90 @@ server {
92101 return 403;
93102 }
94103
95- # ── Root — bare domain status ──────────────────────────────────────────────
104+
105+ # ─────────────────────────────────────────────────────────────────────────
106+ # Root status
107+ # ─────────────────────────────────────────────────────────────────────────
96108 location = / {
97109 default_type application/json;
98110 return 200 '{"service":"FieldTrack 2.0","status":"online"}';
99111 }
100112
101- # ── Health check (deployment verification + external monitors) ─────────────
113+
114+ # ─────────────────────────────────────────────────────────────────────────
115+ # Health endpoint
116+ # ─────────────────────────────────────────────────────────────────────────
102117 location = /health {
118+
103119 limit_req zone=fieldtrack_health burst=10 nodelay;
104120
105121 proxy_pass http://fieldtrack_backend;
106- proxy_set_header Host $host;
107- proxy_set_header X-Real-IP $remote_addr;
108- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
122+
123+ proxy_set_header Host $host;
124+ proxy_set_header X-Real-IP $remote_addr;
125+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
109126 proxy_set_header X-Forwarded-Proto $scheme;
110127 }
111128
112- # ── API route (production backend — strips /api prefix) ────────────────────
129+
130+ # ─────────────────────────────────────────────────────────────────────────
131+ # API
132+ # strips /api prefix
133+ # ─────────────────────────────────────────────────────────────────────────
113134 location /api/ {
135+
114136 limit_req zone=fieldtrack_api burst=50 nodelay;
115137
116- # Strip /api prefix: /api/users → /users
117138 rewrite ^/api/(.*) /$1 break;
118139
119140 proxy_pass http://fieldtrack_backend;
120141
121- # Standard proxy headers
122- proxy_set_header Host $host;
123- proxy_set_header X-Real-IP $remote_addr;
124- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
142+ proxy_set_header Host $host;
143+ proxy_set_header X-Real-IP $remote_addr;
144+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
125145 proxy_set_header X-Forwarded-Proto $scheme;
126- proxy_set_header X-Request-ID $request_id;
146+ proxy_set_header X-Request-ID $request_id;
127147
128- # WebSocket support
129148 proxy_http_version 1.1;
130- proxy_set_header Upgrade $http_upgrade;
149+
150+ proxy_set_header Upgrade $http_upgrade;
131151 proxy_set_header Connection "upgrade";
132152
133- # Timeouts
134153 proxy_connect_timeout 10s;
135- proxy_send_timeout 30s;
136- proxy_read_timeout 30s;
154+ proxy_send_timeout 30s;
155+ proxy_read_timeout 30s;
137156
138- # Buffering
139157 proxy_buffering on;
140158 proxy_buffer_size 4k;
141159 proxy_buffers 8 16k;
142160 }
143161
144- # ── Grafana dashboard (/monitor) ───────────────────────────────────────────
145- # Grafana runs with GF_SERVER_SERVE_FROM_SUB_PATH=true and
146- # GF_SERVER_ROOT_URL=https://fieldtrack.meowsician.tech/monitor
147- # so it handles the /monitor subpath internally. We proxy everything
148- # under /monitor/ to Grafana's root — Grafana does the rest.
162+
163+ # ─────────────────────────────────────────────────────────────────────────
164+ # Grafana dashboard
165+ # ─────────────────────────────────────────────────────────────────────────
149166 location /monitor/ {
167+
150168 proxy_pass http://127.0.0.1:3333/monitor/;
151169
152- proxy_set_header Host $host;
153- proxy_set_header X-Real-IP $remote_addr;
154- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
170+ proxy_set_header Host $host;
171+ proxy_set_header X-Real-IP $remote_addr;
172+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
155173 proxy_set_header X-Forwarded-Proto $scheme;
156174
157- # WebSocket support (Grafana live features)
158175 proxy_http_version 1.1;
159- proxy_set_header Upgrade $http_upgrade;
176+
177+ proxy_set_header Upgrade $http_upgrade;
160178 proxy_set_header Connection "upgrade";
161179
162- # Longer timeouts for dashboard queries
163180 proxy_connect_timeout 10s;
164- proxy_send_timeout 60s;
165- proxy_read_timeout 60s;
181+ proxy_send_timeout 60s;
182+ proxy_read_timeout 60s;
166183 }
167184
168- # Redirect /monitor (no trailing slash) → /monitor/
185+
169186 location = /monitor {
170187 return 301 $scheme://$host/monitor/;
171188 }
189+
172190}
0 commit comments