πŸ”€ Nginx Proxy Pattern for Local Services

Overview

This document establishes a standard pattern for integrating local services (like BlueMap, monitoring dashboards, etc.) into the main website through Nginx reverse proxies. Pages under `/proxy/` forward to local services, while regular URLs are blog posts. This keeps the system modular and sustainable.

Core Principle

Local services running on internal ports are proxied through the main web server at predictable URLs under `/proxy/`. The main website only depends on the proxy endpoint existingβ€”not on the service being available. Graceful degradation is built in.

The Pattern

1. Service Setup

Run your service on a specific internal port (e.g., port 8123 for Dynmap).

Service: Dynmap
Port: 8123
Access: localhost:8123 (local only)

2. Nginx Proxy Configuration

Add a location block to your Nginx config under `/proxy/[service-name]/`:

location /proxy/dynmap/ {
    proxy_pass http://[::1]:8100/;
    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;

    # WebSocket support for live updates
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

3. Public Access

The service is now accessible at:

https://yourdomain.com/proxy/dynmap/

4. Frontend Integration

Use the proxy URL in your frontend and add graceful degradation:

<iframe src="/proxy/dynmap/" id="service-viewer"></iframe>

<script>
fetch('/proxy/dynmap/', { method: 'GET' })
    .then(response => {
        if (!response.ok) showUnavailable();
    })
    .catch(error => showUnavailable());
</script>

Benefits of This Pattern

βœ… Modularity

  • Independent Services: Each service runs independently on its own port
  • Decoupled Integration: The main website doesn't require the service to be available
  • Easy Maintenance: Stop/restart services without restarting the web server

βœ… Sustainability

  • Graceful Degradation: Frontend checks service availability and shows fallback UI
  • No Hard Dependencies: Missing service doesn't break the entire site
  • Clear Architecture: Everyone knows how new services are integrated

βœ… Security & Performance

  • Single Port Exposure: Only the main web server is publicly accessible
  • Header Injection: Proxy injects security headers automatically
  • WebSocket Support: Built-in for real-time services like Dynmap

Real-World Example: Dynmap

Implementation

Service: BlueMap running on [::1]:8100 (IPv6 localhost)

Nginx Config:

location /proxy/dynmap/ {
    proxy_pass http://[::1]:8100/;
    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;

    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

Frontend Usage:

<iframe src="/proxy/dynmap/" id="dynmap-viewer"></iframe>

<script>
// Check if service is available
fetch('/proxy/dynmap/', { method: 'GET' })
    .then(r => !r.ok && showUnavailable())
    .catch(e => showUnavailable());
</script>

Result: If BlueMap isn't running, users see a helpful message instead of a broken interface.

Adding New Services

To integrate a new local service:

  1. Start the service on an internal port (e.g., 9000, 8100, etc.)
  2. Add a location /proxy/[service-name]/ block to Nginx (e.g., /proxy/stats/, /proxy/monitor/)
  3. Reload Nginx: sudo systemctl reload nginx
  4. Create a blog post or page using src="/proxy/[service-name]/"
  5. Add availability checking via fetch/GET request
  6. Provide graceful fallback UI if service is unavailable

πŸ“ URL Naming Convention

All proxied local services use the /proxy/ prefix:

  • /proxy/dynmap/ - Minecraft live map (BlueMap)
  • /proxy/stats/ - System statistics dashboard
  • /proxy/monitor/ - Service monitoring tool

Clear separation: Regular URLs (e.g., /chess-game/) are blog posts, while /proxy/* URLs are forwarded services.

Checklist for New Service Integration

βœ“ Service running on internal port (e.g., 8100, 9000)
βœ“ Nginx location block added to /etc/nginx/sites-available/blog
βœ“ Nginx config tested (sudo nginx -t)
βœ“ Nginx reloaded (sudo systemctl reload nginx)
βœ“ Frontend iframe/embed added with /proxy/[service-name]/ URL
βœ“ Service availability check implemented (fetch GET request)
βœ“ Graceful unavailable UI created with fallback message
βœ“ Documentation updated

Troubleshooting

Service accessible locally but not via /proxy/[service]/

  • Check Nginx config: sudo nginx -t
  • Verify proxy_pass URL matches service location (check IPv4 vs IPv6)
  • Service may listen on IPv6 (::) - use http://[::1]:PORT/ in proxy_pass
  • Check firewall rules if applicable
  • Verify Nginx was reloaded after config change

CORS or WebSocket issues

  • Ensure headers are properly set in proxy block
  • Include Upgrade and Connection headers for WebSocket
  • Reload Nginx after config changes

Service shows unavailable even when running

  • Verify service is actually responding: curl http://localhost:[PORT]/
  • Check CORS headers if service requires them
  • Browser console may show CORS errors - check service CORS config