Nginx Development Setup Guide
This guide provides sample nginx configurations for development environments where you want to use nginx as a reverse proxy instead of relying on the built-in development servers.
Overview
While the tracker system uses AWS ELB in production, nginx is commonly used in development environments for:
- Local Development: Testing the full stack with a reverse proxy
- Integration Testing: Simulating production-like routing
- CORS Handling: Avoiding cross-origin issues during development
- Static File Serving: Efficient serving of frontend assets
Basic Development Configuration
Simple Reverse Proxy
For basic development setups, here's a minimal nginx configuration:
# /etc/nginx/sites-available/tracker-dev
server {
listen 80;
server_name localhost tracker.local;
# Global resolver for DNS resolution
resolver 127.0.0.11 ipv6=off; # Docker's internal DNS
resolver_timeout 5s;
# Basic security headers
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
# API Backend (FastAPI)
location /api/ {
proxy_pass http://localhost:8100/api/;
proxy_http_version 1.1;
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;
}
# Static files from API
location /static/ {
proxy_pass http://localhost:8100/static/;
proxy_set_header Host $host;
expires 1d;
}
# Admin Panel
location /admin/ {
proxy_pass http://localhost:3000/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
# User Frontend (default)
location / {
proxy_pass http://localhost:3100/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
Docker Compose Integration
For Docker-based development, use service names instead of localhost:
# nginx.conf for Docker Compose
server {
listen 80;
server_name localhost;
# Use Docker's internal DNS
resolver 127.0.0.11 ipv6=off;
# API Backend
location /api/ {
proxy_pass http://api:8100/api/;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Static files
location /static/ {
proxy_pass http://api:8100/static/;
expires 7d;
}
# Admin Panel
location /admin/ {
proxy_pass http://tracker-admin-dev:3000/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_cache_bypass $http_upgrade;
}
# User Frontend
location / {
proxy_pass http://tracker-frontend-dev:3100/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_cache_bypass $http_upgrade;
}
}
Production-Like Configuration
Enhanced Security Headers
For testing production-like security:
server {
listen 80;
server_name tracker.local;
# Enhanced security headers
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
add_header Referrer-Policy "strict-origin-when-cross-origin";
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://$host;";
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
gzip_min_length 1000;
gzip_comp_level 6;
# API with enhanced headers
location /api/ {
proxy_pass http://api:8100/api/;
proxy_http_version 1.1;
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;
# Handle CORS preflight
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
}
# Static files with caching
location /static/ {
proxy_pass http://api:8100/static/;
proxy_set_header Host $host;
expires 7d;
add_header Cache-Control "public, max-age=604800";
}
# Frontend applications
location /admin/ {
proxy_pass http://tracker-admin-dev:3000/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
location / {
proxy_pass http://tracker-frontend-dev:3100/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
# Error pages
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
}
Service-Specific Configurations
Admin Panel Only
If you only want to proxy the admin panel:
server {
listen 80;
server_name admin.tracker.local;
# API requests
location /api/ {
proxy_pass http://api:8100/api/;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Static files
location /static/ {
proxy_pass http://api:8100/static/;
expires 7d;
}
# Admin panel
location / {
proxy_pass http://tracker-admin-dev:3000/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_cache_bypass $http_upgrade;
}
}
API Gateway Pattern
For microservices-style routing:
# API Gateway configuration
upstream api_backend {
server api:8100;
}
upstream geocoding_service {
server geocoding-service:8002;
}
upstream tracker_fetcher {
server tracker-fetcher:8001;
}
server {
listen 80;
server_name api.tracker.local;
# Main API
location /api/v1/ {
proxy_pass http://api_backend/api/v1/;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Geocoding service
location /api/geocoding/ {
proxy_pass http://geocoding_service/;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Tracker fetcher service
location /api/fetcher/ {
proxy_pass http://tracker_fetcher/;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Health checks
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}
Docker Compose Integration
Adding Nginx to Docker Compose
Add nginx to your development docker-compose.yml:
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./ssl:/etc/nginx/ssl:ro # Optional: for HTTPS
depends_on:
- api
- tracker-admin-dev
- tracker-frontend-dev
networks:
- tracker-network
# Your existing services...
api:
# ... existing configuration
tracker-admin-dev:
# ... existing configuration
tracker-frontend-dev:
# ... existing configuration
networks:
tracker-network:
driver: bridge
Environment-Specific Configuration
Use environment variables for flexibility:
# nginx.conf with environment variables
server {
listen 80;
server_name ${NGINX_HOST};
location /api/ {
proxy_pass http://${API_HOST}:${API_PORT}/api/;
proxy_set_header Host $host;
}
location /admin/ {
proxy_pass http://${ADMIN_HOST}:${ADMIN_PORT}/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
}
location / {
proxy_pass http://${FRONTEND_HOST}:${FRONTEND_PORT}/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
}
}
Then use envsubst in your Docker entrypoint:
# Dockerfile for custom nginx
FROM nginx:alpine
COPY nginx.conf.template /etc/nginx/conf.d/default.conf.template
COPY docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]
#!/bin/sh
# docker-entrypoint.sh
envsubst '${NGINX_HOST} ${API_HOST} ${API_PORT} ${ADMIN_HOST} ${ADMIN_PORT} ${FRONTEND_HOST} ${FRONTEND_PORT}' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf
exec "$@"
HTTPS Development Setup
Self-Signed Certificates
For HTTPS testing in development:
# Generate self-signed certificate
mkdir -p ssl
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout ssl/tracker.key \
-out ssl/tracker.crt \
-subj "/C=US/ST=State/L=City/O=Organization/CN=tracker.local"
# HTTPS configuration
server {
listen 443 ssl http2;
server_name tracker.local;
ssl_certificate /etc/nginx/ssl/tracker.crt;
ssl_certificate_key /etc/nginx/ssl/tracker.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
# Your location blocks here...
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name tracker.local;
return 301 https://$server_name$request_uri;
}
Troubleshooting
Common Issues
DNS Resolution Problems:
# Add to server block
resolver 127.0.0.11 ipv6=off; # Docker DNS
resolver_timeout 5s;
WebSocket Connection Issues:
# Ensure these headers are set for frontend proxying
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_cache_bypass $http_upgrade;
CORS Issues:
# Add CORS headers for API requests
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
Testing Configuration
# Test nginx configuration
nginx -t
# Reload configuration
nginx -s reload
# Check if services are accessible
curl -I http://localhost/api/v1/health
curl -I http://localhost/admin/
curl -I http://localhost/
Logging and Debugging
# Enhanced logging
error_log /var/log/nginx/error.log debug;
access_log /var/log/nginx/access.log combined;
# Log proxy details
location /api/ {
proxy_pass http://api:8100/api/;
# Debug headers
proxy_set_header X-Debug-Original-URI $request_uri;
proxy_set_header X-Debug-Proxy-Host $proxy_host;
# Log upstream response
add_header X-Upstream-Status $upstream_status always;
}
Best Practices
Development
- Use service names in Docker Compose environments
- Enable debug logging for troubleshooting
- Keep configurations simple and readable
- Use environment variables for flexibility
Security
- Always set appropriate headers
- Use HTTPS for production-like testing
- Implement proper CORS policies
- Validate upstream responses
Performance
- Enable gzip compression
- Set appropriate cache headers
- Use HTTP/2 when possible
- Monitor upstream health
This configuration provides a solid foundation for nginx-based development environments while maintaining compatibility with the production AWS ELB setup.