Skip to content

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.