How to Properly Configure Nginx Reverse Proxy for Applications with Static Files Under Subpaths


2 views

When setting up Nginx as a reverse proxy for multiple applications, developers often encounter static file loading issues when applications are mounted under subpaths (like /app1, /app2). The root cause lies in how web applications typically generate absolute paths for static assets.

Consider this typical scenario:

location /app1 {
    proxy_pass http://127.0.0.1:9001/;
}

While this routes requests to your application server, static assets break because:

  1. The application generates static file URLs relative to root (/static/...)
  2. Browser requests omit the /app1 prefix

We need to address both the reverse proxy configuration and application settings:

1. Nginx Configuration

server {
    listen 80;
    server_name mywebserver;

    # Handle static files
    location /app1/static/ {
        alias /path/to/app1/static/;
        expires 30d;
        access_log off;
    }

    # Proxy configuration
    location /app1/ {
        proxy_pass http://127.0.0.1:9001/;
        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;
        
        # Important for applications that generate URLs
        proxy_redirect ~^/(.*)$ /app1/$1;
    }
}

2. Application Configuration

Most modern web frameworks support base URL configuration:

For Django:

# settings.py
FORCE_SCRIPT_NAME = '/app1'
STATIC_URL = FORCE_SCRIPT_NAME + '/static/'

For Flask:

app = Flask(__name__)
app.config['APPLICATION_ROOT'] = '/app1'

3. Alternative Approach: URL Rewriting

For applications you can't modify, use Nginx sub_filter:

location /app1/ {
    proxy_pass http://127.0.0.1:9001/;
    proxy_set_header Host $host;
    
    sub_filter_once off;
    sub_filter 'src="/' 'src="/app1/';
    sub_filter 'href="/' 'href="/app1/';
}

Verify with curl:

curl -I http://mywebserver/app1/static/css/main.css
curl -v http://mywebserver/app1/api/health

Check for proper Content-Type headers and 200 responses for static files, while API calls should proxy through to your application.

  • Enable gzip for static assets
  • Set proper cache headers
  • Consider using try_files for static assets before proxying
location /app1/ {
    try_files $uri @proxy_app1;
}

location @proxy_app1 {
    proxy_pass http://127.0.0.1:9001;
}

When setting up multiple web applications behind an Nginx reverse proxy with URL prefixes, developers often encounter static file loading issues. The root cause lies in how application paths are constructed versus how Nginx routes requests. Here's a deep dive into solving this properly.

# Current non-working setup
server {
  listen 80;
  server_name mywebserver;

  location /app1 {
    proxy_pass http://127.0.0.1:9001/;
    # Missing static files handling
  }
}

The key symptoms you'll observe in browser DevTools:

  • 404 errors for JavaScript/CSS files
  • Requests going to root (/) instead of prefixed paths (/app1)
  • Mixed content warnings if using absolute paths

Here's the proper configuration that handles both proxying and static assets:

server {
  listen 80;
  server_name mywebserver;

  # App1 configuration
  location /app1 {
    proxy_pass http://127.0.0.1:9001/;
    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 static files
    location /app1/static/ {
      alias /path/to/app1/static/;
      expires 30d;
      access_log off;
    }
  }

  # App2 configuration
  location /app2 {
    proxy_pass http://127.0.0.1:9002/;
    # Similar proxy headers...
    
    location /app2/assets/ {
      alias /path/to/app2/assets/;
      # Additional static file rules
    }
  }
}

For some frameworks, you'll need additional configuration:

Django Example

# settings.py
FORCE_SCRIPT_NAME = '/app1'
STATIC_URL = FORCE_SCRIPT_NAME + '/static/'

React/Vue SPA Solution

// vue.config.js
module.exports = {
  publicPath: process.env.NODE_ENV === 'production'
    ? '/app1/'
    : '/'
}

For applications that can't be easily configured:

# Rewrite static file paths
location /app1 {
  proxy_pass http://127.0.0.1:9001/;
  
  # Handle embedded paths
  sub_filter_once off;
  sub_filter 'href="/' 'href="/app1/';
  sub_filter 'src="/' 'src="/app1/';
  sub_filter 'url(/' 'url(/app1/';
}
  • Check Nginx error logs: tail -f /var/log/nginx/error.log
  • Verify path mappings with curl -v http://localhost/app1
  • Use Chrome DevTools Network tab to inspect requests