Skip to main content

Overview

Nginx acts as the single public entry point for SGIVU in production, serving as a reverse proxy that:
  • Separates Auth Server (OIDC/token issuance) from Gateway (BFF + API routing)
  • Routes requests to appropriate backend services
  • Serves the Angular SPA from S3 as a catch-all fallback
  • Enables independent scaling and deployment of components
  • Simplifies firewall rules (only port 80/443 exposed)

Configuration Location

File: infra/nginx/sites-available/default.conf Installation:
# Copy to Nginx sites directory
sudo cp infra/nginx/sites-available/default.conf /etc/nginx/sites-available/default

# Enable site (if using sites-enabled pattern)
sudo ln -sf /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default

# Test configuration
sudo nginx -t

# Reload Nginx
sudo systemctl reload nginx

Architecture

┌─────────────┐
│   Client    │
│  (Browser)  │
└──────┬──────┘
       │ HTTP/HTTPS
       │ Port 80/443

┌─────────────────────┐
│       Nginx         │
│  (EC2 Instance)     │
└──┬───────────────┬──┘
   │               │
   │ /oauth2/*     │ /v1/*
   │ /login        │ /auth/session
   │               │
   ▼               ▼
┌────────┐    ┌────────────┐
│ Auth   │    │  Gateway   │
│ :9000  │    │   :8080    │
└────────┘    └─────┬──────┘
                    │ lb://

              ┌──────────────┐
              │ Microservices│
              │ (via Eureka) │
              └──────────────┘

Upstream Definitions

Gateway Upstream

upstream gateway {
    server 127.0.0.1:8080;
    keepalive 32;
}
  • Backend: Gateway service (port 8080)
  • Keepalive: Reuses TCP connections (reduces latency)

Auth Upstream

upstream auth {
    server 127.0.0.1:9000;
    keepalive 32;
}
  • Backend: Auth Server service (port 9000)
  • Keepalive: Reuses TCP connections
In Docker Compose production setup, these services are accessed via Docker network. If using containers, change to server sgivu-gateway:8080 and server sgivu-auth:9000.

Server Configuration

Basic Settings

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    
    server_name ec2-98-86-100-220.compute-1.amazonaws.com _;
    
    access_log /var/log/nginx/sgivu-access.log;
    error_log /var/log/nginx/sgivu-error.log;
    
    # Support vehicle image uploads (~5-10MB each, multiple per request)
    client_max_body_size 50M;
    
    # High timeouts: ML service can take >30s for complex inferences
    proxy_connect_timeout 60s;
    proxy_send_timeout 60s;
    proxy_read_timeout 60s;
}
Update server_name to match your EC2 hostname or domain. Without Elastic IP, EC2 hostname changes on restart.

Proxy Headers

proxy_http_version 1.1;
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;
proxy_set_header X-Forwarded-Port $server_port;
Purpose:
  • HTTP/1.1: Required for keepalive with upstreams
  • X-Real-IP: Client’s actual IP (for logging, rate limiting)
  • X-Forwarded-For: Proxy chain (for auditing)
  • X-Forwarded-Proto: Original protocol (http/https)
  • X-Forwarded-Port: Original port (80/443)

Routing Rules

Auth Server Routes (Port 9000)

Auth Server handles OAuth2/OIDC flows and login UI. These routes bypass Gateway to avoid circular dependencies (Gateway validates tokens issued by Auth).

Login Page

location /login {
    proxy_pass http://auth/login;
    proxy_set_header Host $host;
    proxy_set_header Connection "";
}

OAuth2 Endpoints

location ~ ^/oauth2/(authorize|token|jwks|introspect|revoke) {
    proxy_pass http://auth;
    proxy_set_header Host $host;
    proxy_set_header Connection "";
}
Endpoints:
  • /oauth2/authorize: Authorization code flow
  • /oauth2/token: Token exchange
  • /oauth2/jwks: JSON Web Key Set
  • /oauth2/introspect: Token introspection
  • /oauth2/revoke: Token revocation

OIDC Discovery

location ~ ^/(\.well-known/openid-configuration|userinfo) {
    proxy_pass http://auth;
    proxy_set_header Host $host;
    proxy_set_header Connection "";
}
Endpoints:
  • /.well-known/openid-configuration: OIDC metadata
  • /userinfo: User information endpoint

Static Assets

location ^~ /js/ {
    proxy_pass http://auth/js/;
    proxy_set_header Host $host;
    proxy_set_header Connection "";
}

location ^~ /css/ {
    proxy_pass http://auth/css/;
    proxy_set_header Host $host;
    proxy_set_header Connection "";
}

location ^~ /assets/ {
    proxy_pass http://auth/assets/;
    proxy_set_header Host $host;
    proxy_set_header Connection "";
}

location ^~ /images/ {
    proxy_pass http://auth/images/;
    proxy_set_header Host $host;
    proxy_set_header Connection "";
}

location = /favicon.ico {
    proxy_pass http://auth/favicon.ico;
    proxy_set_header Host $host;
    proxy_set_header Connection "";
}
^~ prefix ensures these routes have priority over the S3 fallback, preventing the SPA from capturing Auth Server assets.

Logout Endpoint

location ~ ^/connect/logout {
    proxy_pass http://auth;
    proxy_set_header Host $host;
    proxy_set_header Connection "";
}

Actuator Endpoints

location /auth/actuator/ {
    proxy_pass http://auth/actuator/;
    proxy_set_header Host $host;
    proxy_set_header Connection "";
}
Health Check: GET /auth/actuator/health

Gateway Routes (Port 8080)

Gateway routes business APIs to microservices via Eureka and implements the BFF pattern for session management.

Business APIs

location /v1/ {
    proxy_pass http://gateway/v1/;
    proxy_set_header Host $host;
    proxy_set_header Connection "";
    # WebSocket-ready for real-time notifications
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}
API Routes (defined in Gateway’s GatewayRoutesConfig):
  • /v1/users/**lb://sgivu-user
  • /v1/persons/**lb://sgivu-user
  • /v1/clients/**lb://sgivu-client
  • /v1/companies/**lb://sgivu-client
  • /v1/vehicles/**lb://sgivu-vehicle
  • /v1/purchase-sales/**lb://sgivu-purchase-sale
  • /v1/ml/**http://sgivu-ml:8000
Filters Applied:
  • TokenRelay: Propagates OAuth2 access token
  • CircuitBreaker: Resilience4j circuit breaker
  • Fallback: forward:/fallback/{service}

BFF Session Endpoint

location = /auth/session {
    proxy_pass http://gateway/auth/session;
    proxy_set_header Host $host;
    proxy_set_header Connection "";
}
Purpose: SPA queries session state without exposing tokens Response:
{
  "subject": "user-uuid",
  "username": "steven",
  "roles": ["ADMIN"],
  "isAdmin": true
}

OAuth2 Client Flows

location ~ ^/(oauth2/authorization/|login/oauth2/code/) {
    proxy_pass http://gateway;
    proxy_set_header Host $host;
    proxy_set_header Connection "";
}
Purpose: Gateway as OAuth2 Client (authorization_code + PKCE)

Logout and Session Management

location ~ ^/(logout|session) {
    proxy_pass http://gateway;
    proxy_set_header Host $host;
    proxy_set_header Connection "";
}

Swagger UI Documentation

Each microservice has dedicated Swagger UI routes:
location = /docs/user {
    return 302 /docs/user/swagger-ui.html;
}

location /docs/user/ {
    proxy_pass http://gateway/docs/user/;
    proxy_set_header Host $host;
    proxy_set_header Connection "";
    proxy_redirect / /docs/user/;
    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;
    proxy_set_header X-Forwarded-Port $server_port;
}
Available Documentation:
  • /docs/auth/: Auth Server API
  • /docs/user/: User service API
  • /docs/client/: Client service API
  • /docs/vehicle/: Vehicle service API
  • /docs/purchase-sale/: Purchase-sale service API
  • /docs/gateway/: Gateway routes
OpenAPI Specs:
location /v3/api-docs/ {
    proxy_pass http://gateway/v3/api-docs;
    proxy_set_header Host $host;
    proxy_set_header Connection "";
}
Swagger UI uses the Referer header to resolve service-specific OpenAPI specs. Gateway routes based on this header.

Actuator Endpoints

location /gateway/actuator/ {
    proxy_pass http://gateway/actuator/;
    proxy_set_header Host $host;
    proxy_set_header Connection "";
}
Health Check: GET /gateway/actuator/health

Observability Routes

These endpoints are exposed without authentication. In production, restrict access via IP whitelist, VPN, or basic auth.

Eureka Dashboard

location /eureka/ {
    proxy_pass http://127.0.0.1:8761/;
    proxy_set_header Host $host;
    proxy_set_header Connection "";
}
Purpose: View registered service instances URL: http://your-ec2-hostname/eureka/

Zipkin UI

location /zipkin/ {
    proxy_pass http://127.0.0.1:9411/zipkin/;
    proxy_set_header Host $host;
    proxy_set_header Connection "";
}
Purpose: Distributed tracing and request correlation URL: http://your-ec2-hostname/zipkin/

Frontend (S3 Catch-All)

location / {
    proxy_pass http://sgivu-frontend.s3-website-us-east-1.amazonaws.com/;
    # S3 virtual-hosted style requires Host = bucket name
    proxy_set_header Host sgivu-frontend.s3-website-us-east-1.amazonaws.com;
    proxy_set_header Connection "";
    
    # Assets with hash in filename: immutable, aggressive caching
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
        proxy_pass http://sgivu-frontend.s3-website-us-east-1.amazonaws.com;
        proxy_set_header Host sgivu-frontend.s3-website-us-east-1.amazonaws.com;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
    
    # SPA routing: return index.html for non-existent routes
    proxy_intercept_errors on;
    error_page 404 = /index.html;
}
Behavior:
  • All unmatched routes go to S3
  • Angular Router handles client-side routing
  • Assets cached for 1 year (immutable hash-based filenames)
  • 404 errors return index.html for SPA routing
Order matters! More specific routes (Auth, Gateway) must be defined before the catch-all / location.

Shared Resources

Swagger UI Assets

location /swagger-ui/ {
    proxy_pass http://gateway/swagger-ui/;
    proxy_set_header Host $host;
    proxy_set_header Connection "";
}

location /webjars/ {
    proxy_pass http://gateway/webjars/;
    proxy_set_header Host $host;
    proxy_set_header Connection "";
}
Purpose: Shared Swagger UI resources loaded by all microservice documentation pages

Security Considerations

HTTPS Configuration

For production, enable HTTPS with SSL/TLS certificates:
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    
    ssl_certificate /etc/letsencrypt/live/your-domain/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your-domain/privkey.pem;
    
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    
    # ... rest of configuration
}

# Redirect HTTP to HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name your-domain.com;
    return 301 https://$server_name$request_uri;
}

Rate Limiting

http {
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
    
    server {
        location /v1/ {
            limit_req zone=api_limit burst=20 nodelay;
            # ... proxy_pass configuration
        }
    }
}

IP Whitelisting (Observability)

location /eureka/ {
    allow 10.0.0.0/8;      # Internal network
    allow 203.0.113.0/24;  # Office IP range
    deny all;
    
    proxy_pass http://127.0.0.1:8761/;
}

location /zipkin/ {
    allow 10.0.0.0/8;
    allow 203.0.113.0/24;
    deny all;
    
    proxy_pass http://127.0.0.1:9411/zipkin/;
}

Basic Authentication

Protect admin endpoints:
location /actuator/ {
    auth_basic "Restricted Access";
    auth_basic_user_file /etc/nginx/.htpasswd;
    
    proxy_pass http://gateway/actuator/;
}
Generate password file:
sudo htpasswd -c /etc/nginx/.htpasswd admin

Performance Tuning

Keepalive Connections

upstream gateway {
    server 127.0.0.1:8080;
    keepalive 32;  # Keep 32 idle connections
}

Timeouts

proxy_connect_timeout 60s;  # Time to establish connection
proxy_send_timeout 60s;     # Time to send request
proxy_read_timeout 60s;     # Time to receive response
ML service endpoints may take >30s for complex predictions. Adjust timeouts accordingly.

Buffering

proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;

Compression

gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
gzip_min_length 1000;
gzip_comp_level 6;

Monitoring

Access Logs

access_log /var/log/nginx/sgivu-access.log;
error_log /var/log/nginx/sgivu-error.log;
View Logs:
# Real-time access log
sudo tail -f /var/log/nginx/sgivu-access.log

# Error log
sudo tail -f /var/log/nginx/sgivu-error.log

# Filter specific status codes
grep "500" /var/log/nginx/sgivu-access.log

Status Module

Enable Nginx status:
location /nginx_status {
    stub_status on;
    access_log off;
    allow 127.0.0.1;
    deny all;
}
Check Status:
curl http://localhost/nginx_status

Troubleshooting

502 Bad Gateway

Problem: Nginx cannot reach backend service Solutions:
  1. Verify service is running:
    docker compose ps sgivu-gateway
    curl http://localhost:8080/actuator/health
    
  2. Check upstream configuration:
    sudo nginx -t
    
  3. Review error logs:
    sudo tail -f /var/log/nginx/sgivu-error.log
    

504 Gateway Timeout

Problem: Backend service took too long to respond Solutions:
  1. Increase timeouts:
    proxy_read_timeout 120s;
    
  2. Check backend service logs:
    docker compose logs -f sgivu-ml
    

SSL Certificate Errors

Problem: “NET::ERR_CERT_AUTHORITY_INVALID” Solutions:
  1. Verify certificate paths:
    sudo ls -l /etc/letsencrypt/live/your-domain/
    
  2. Renew Let’s Encrypt certificate:
    sudo certbot renew
    sudo systemctl reload nginx
    

Configuration Test Failed

# Test configuration
sudo nginx -t

# Check syntax errors
sudo nginx -T | less

Reload Without Downtime

# Graceful reload (zero-downtime)
sudo systemctl reload nginx

# Or
sudo nginx -s reload

Next Steps