An Nginx reverse proxy sits between clients and one or more backend application servers. Clients connect to Nginx, Nginx forwards the request to the appropriate backend, and the backend response returns through Nginx to the client.

This example uses Apache Tomcat on port 8080 as the backend, but the same pattern works with Spring Boot, Node.js, Express, Jetty, Open Liberty and other HTTP application servers.

What does an Nginx reverse proxy do?

A reverse proxy gives an application a single public entry point while keeping backend services behind it. Nginx can route requests by hostname or path, terminate TLS, serve static files, buffer responses, cache content and distribute traffic across multiple upstream servers.

In this tutorial, requests to /examples/ arrive at Nginx on port 80 and are forwarded to Tomcat at 127.0.0.1:8080.

Browser
   |
   | https://example.com/examples/
   v
Nginx :80
   |
   | proxy_pass
   v
Tomcat :8080
   |
   v
/examples/

Install Nginx on Ubuntu

Install Nginx from Ubuntu's package repository:

sudo apt update
sudo apt install nginx -y

Then verify that the service is running:

sudo systemctl status nginx

You can also display the installed Nginx version:

nginx -v

If you specifically need the newest Nginx stable release rather than the version packaged by your Ubuntu release, use the official Nginx repository. For most server deployments, the important requirement is to stay on a currently supported and patched release.

Create an Nginx reverse proxy configuration

Rather than putting every change into the distribution's default virtual host, create a dedicated server configuration. On a typical Ubuntu installation, create:

sudo nano /etc/nginx/sites-available/tomcat-proxy

Add this configuration:

server {
    listen 80;
    server_name example.com;

    location /examples/ {
        proxy_pass https://127.0.0.1:8080/examples/;

        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;
    }
}

Replace example.com with your hostname. For a local test where the hostname is unimportant, you can use server_name _;.

Understand the proxy_pass URI

The trailing slash in a proxy_pass configuration matters. In this example, both the location and upstream URI end in /examples/:

location /examples/ {
    proxy_pass https://127.0.0.1:8080/examples/;
}

A request such as:

https://example.com/examples/index.html

is proxied to:

https://127.0.0.1:8080/examples/index.html

Be deliberate about whether proxy_pass includes a URI. Nginx handles URI replacement differently when the directive contains a path, and an accidental trailing-slash change can send a different URI to the backend than you intended.

Forward the original request information

A backend application often needs to know the original host, client address and protocol. These headers provide that information:

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;

X-Forwarded-For is particularly useful because $proxy_add_x_forwarded_for appends the current client address to an existing forwarded-for chain rather than simply replacing it.

The backend must still be configured to trust forwarded headers only from known proxies. Do not blindly trust client-supplied forwarding headers when an application is directly reachable from untrusted networks.

Enable the Ubuntu Nginx site

Create a symbolic link in sites-enabled:

sudo ln -s /etc/nginx/sites-available/tomcat-proxy \
  /etc/nginx/sites-enabled/tomcat-proxy

If the default site conflicts with your new virtual host, disable it:

sudo rm /etc/nginx/sites-enabled/default

Test before you reload Nginx

Do not restart Nginx immediately after editing the configuration. First validate the complete configuration:

sudo nginx -t

A successful test reports that the syntax is valid and the configuration test succeeded.

Then reload Nginx:

sudo systemctl reload nginx

A reload is normally preferable to a restart for configuration changes because Nginx can apply the new configuration without unnecessarily stopping the service.

Test the reverse proxy

First confirm that Tomcat itself responds:

curl -I https://127.0.0.1:8080/examples/

Then make the same request through Nginx:

curl -I https://example.com/examples/

If the backend works directly but the proxied request fails, inspect the Nginx logs:

sudo tail -f /var/log/nginx/error.log

Should you disable proxy buffering?

Nginx proxy buffering is enabled by default, and for ordinary web applications that is usually desirable. It lets Nginx receive a backend response efficiently and shields the application server from slow clients.

Do not disable buffering simply because the server is acting as a reverse proxy. Turn it off only when the application's behavior requires streaming or very low-latency delivery, such as some server-sent event or streaming workloads:

location /stream/ {
    proxy_pass https://127.0.0.1:8080/stream/;
    proxy_buffering off;
}

Proxy multiple backend applications

Nginx can route different paths to different applications. For example:

server {
    listen 80;
    server_name example.com;

    location /java/ {
        proxy_pass https://127.0.0.1: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;
    }

    location /api/ {
        proxy_pass https://127.0.0.1:3000/;
        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;
    }
}

In this configuration, Nginx provides one public server while routing Java requests to one backend and API requests to another.

Use an upstream block for backend servers

For a larger configuration, define the backend separately with an upstream block:

upstream tomcat_backend {
    server 127.0.0.1:8080;
}

server {
    listen 80;
    server_name example.com;

    location /examples/ {
        proxy_pass https://tomcat_backend/examples/;
        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;
    }
}

This separates routing from backend definitions and makes it easier to add additional application-server instances later.

Add HTTPS in production

A public production reverse proxy should normally terminate HTTPS rather than expose an application over plain HTTP. Once a TLS certificate is installed, Nginx can listen on port 443 and continue forwarding traffic to an internal HTTP backend.

The backend can use X-Forwarded-Proto to determine that the original client connection used HTTPS even when Nginx communicates with the internal application over HTTP.

Complete Nginx reverse proxy example

For a straightforward Tomcat deployment, the essential configuration is:

server {
    listen 80;
    server_name example.com;

    location /examples/ {
        proxy_pass https://127.0.0.1:8080/examples/;
        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;
    }
}

After saving the configuration, validate and reload it:

sudo nginx -t
sudo systemctl reload nginx

With Tomcat listening on port 8080, requests to https://example.com/examples/ now pass through Nginx before reaching the Java application server.

That basic pattern is the foundation for more advanced reverse-proxy architectures that add HTTPS termination, multiple upstream servers, load balancing, caching, rate limiting and application-specific routing.