Robin.te.Hofstee

Reverse Proxying Multiple Self-Hosted Apps with One Nginx Instance

Robin te Hofstee8 min readViews2
Reverse Proxying Multiple Self-Hosted Apps with One Nginx Instance

Learn how to use a single Nginx instance to reverse proxy multiple self-hosted applications, manage subdomains, and secure them with HTTPS.

Reverse Proxying Multiple Self-Hosted Apps with One Nginx Instance

Running multiple self-hosted applications on a single server (or a small network) can quickly become a tangled mess. Each app typically needs its own port open to the internet, leading to a potential security nightmare and a painful configuration process. This tutorial demonstrates how to streamline this by using Nginx as a reverse proxy, allowing you to route all traffic to your apps through a single, manageable entry point.

What is a Reverse Proxy and Why Use One?

A reverse proxy sits in front of one or more backend servers, intercepting client requests and forwarding them to the appropriate server. From the client’s perspective, it appears as the backend server itself. The key benefits of this approach include:

  • Security: Hides the internal structure of your network, preventing direct access to your applications.
  • Load Balancing: (Although not the focus here, Nginx can handle some basic load balancing)
  • SSL Termination: Handles HTTPS encryption, offloading this processing from your backend apps.
  • Centralized Management: Simplifies configuration and updates for multiple applications.

Instead of exposing each app on its own port (e.g., 8080, 3000, 5000), Nginx acts as a single point of contact, routing requests based on the domain name or URL path.

Subdomain Routing: The Core Concept

The core of this setup is subdomain routing. We'll use subdomains (e.g., blog.example.com, dashboard.example.com) to differentiate between our applications. Nginx listens on ports 80 and 443 and, based on the subdomain in the incoming request, directs the traffic to the correct backend application running on a specific port. This provides a cleaner, more organized, and more scalable solution.

DNS Setup: Pointing Subdomains to Your Server

Before you configure Nginx, you need to configure your DNS records. This tells your domain where to find your server.

  • A Records: Create an A record for each subdomain pointing to the public IP address of your server. For example:
    • blog.example.com. A [Your Server's IP Address]
    • dashboard.example.com. A [Your Server's IP Address]
    • notes.example.com. A [Your Server's IP Address]
  • Wildcard DNS Entry (Recommended): A wildcard DNS entry like *.example.com. A [Your Server's IP Address] is often simpler to manage. This automatically handles any subdomain you add in the future. This is especially beneficial when you have numerous applications.

Nginx's Server Block Structure

Nginx's configuration is built around server blocks (also known as virtual hosts). Each server block defines how Nginx handles requests for a specific domain or set of domains.

The key directives within a server block are:

  • server_name: This directive specifies the domain name(s) that the server block should handle. This is crucial for subdomain routing.
  • proxy_pass: This directive tells Nginx where to forward the request – to the address and port of your backend application.

Here’s an example server block:

server {
    listen 80;
    server_name blog.example.com;
 
    location / {
        proxy_pass http://localhost:3000;  # Replace with your app's address
        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;
    }
}

Repeating the Pattern for Multiple Apps

Let's create server blocks for three example applications: app1.example.com, app2.example.com, and app3.example.com.

1. app1.example.com (Running on Port 8080):

server {
    listen 80;
    server_name app1.example.com;
 
    location / {
        proxy_pass http://localhost:8080;
        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;
    }
}

2. app2.example.com (Running on Port 5000):

server {
    listen 80;
    server_name app2.example.com;
 
    location / {
        proxy_pass http://localhost:5000;
        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;
    }
}

3. app3.example.com (Running on Port 9000):

server {
    listen 80;
    server_name app3.example.com;
 
    location / {
        proxy_pass http://localhost:9000;
        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;
    }
}

These server blocks should be placed in /etc/nginx/sites-available/ (e.g., app1.example.com, app2.example.com, app3.example.com). Then, create symbolic links to enable them in /etc/nginx/sites-enabled/. For example: ln -s /etc/nginx/sites-available/app1.example.com /etc/nginx/sites-enabled/app1.example.com

Correct Proxy Headers

The proxy_set_header directives are crucial. Here’s why they’re needed:

  • Host: Passes the original Host header from the client to the backend application. Many applications rely on this header to determine the correct domain name or to generate absolute URLs.
  • X-Real-IP: Passes the client’s actual IP address to the backend application. Without this, the app might see Nginx's IP address instead of the user's.
  • X-Forwarded-For: Contains a comma-separated list of IP addresses, starting with the client’s IP and including any intermediate proxies.
  • X-Forwarded-Proto: Indicates whether the original request was HTTP or HTTPS. Essential for apps that need to know if the request was secure.

WebSocket Support

If your applications use WebSockets (e.g., chat apps, real-time dashboards), you need to configure Nginx to handle the Upgrade and Connection headers. This involves modifying the location block to include the proxy_http_version 1.1 directive and adjusting the proxy settings to allow WebSocket connections. This is a more advanced topic and beyond the scope of this introductory tutorial.

Adding HTTPS with Let's Encrypt/Certbot

To secure your applications, you’ll need HTTPS. Let’s Encrypt provides free SSL/TLS certificates. Certbot is a tool that automates the process of obtaining and installing Let’s Encrypt certificates.

Run sudo certbot --nginx -d blog.example.com -d dashboard.example.com -d notes.example.com. Certbot will automatically configure your Nginx server blocks to use the certificates. It uses the --nginx flag to automatically update your Nginx configuration. If you're using wildcard DNS, you can use the wildcard domain here.

Testing and Reloading

Before reloading Nginx, always run nginx -t to test the configuration for syntax errors. This is critical to avoid downtime. After verifying the configuration, reload Nginx: sudo nginx -s reload or sudo systemctl reload nginx.

Troubleshooting

  • 502 Bad Gateway: Often indicates that the backend application is down or that the port specified in the proxy_pass directive is incorrect. Double-check your backend application's status and port configuration.
  • 404 Not Found: Usually caused by a mismatch between the server_name in the Nginx configuration and the requested domain name. Ensure your DNS records are correctly pointing to your server.
  • Mixed Content Warnings: Occur when your website attempts to load resources (images, scripts, stylesheets) over HTTPS while the page itself is served over HTTP. Ensure all resources are served over HTTPS.

This comprehensive guide provides a solid foundation for reverse proxying multiple self-hosted applications with Nginx. Remember to adapt the examples to your specific application configurations and domain names.

Share this post
Robin te Hofstee

Written by Robin te Hofstee

Technology enthusiast with a deep love for programming. Sharing practical guides on React, Next.js, Linux and self-hosting.

Deploy Next.js App to Ubuntu with Apache2

2

A step-by-step guide for deploying a Next.js application on an Ubuntu server with Apache2 as a reverse proxy, including setting up HTTPS with Let's Encrypt and updating deployment processes.