How to Configure Nginx as a Reverse Proxy on RHEL 7

A reverse proxy sits in front of one or more backend application servers, forwarding incoming client requests and returning the backend responses. Nginx is exceptionally well-suited for this role due to its event-driven architecture and low memory footprint. On RHEL 7, a common pattern is to run a Node.js, Python, or Java application on a high-numbered port (such as 3000) and place Nginx on port 80 or 443 to handle SSL termination, load distribution, caching, and header manipulation. This tutorial covers the complete setup of Nginx as a reverse proxy on RHEL 7, including the critical SELinux configuration that trips up many administrators.

Prerequisites

  • RHEL 7 server with root or sudo access
  • A backend application listening on a local port (this guide uses port 3000 as an example)
  • Nginx installed (covered in Step 1)
  • Basic familiarity with Nginx configuration syntax

Step 1: Install Nginx from EPEL

Nginx is available in the EPEL repository for RHEL 7. First enable EPEL if you have not already done so:

sudo yum install -y epel-release
sudo yum install -y nginx

Enable and start the Nginx service:

sudo systemctl enable nginx
sudo systemctl start nginx
sudo systemctl status nginx

Open the required firewall ports:

sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

Step 2: Understand the Nginx Configuration Layout on RHEL 7

On RHEL 7, the main Nginx configuration file is /etc/nginx/nginx.conf. The http block inside that file includes any files matching /etc/nginx/conf.d/*.conf. Best practice is to create a separate file under conf.d/ for each site or application rather than editing the main file directly.

ls /etc/nginx/conf.d/
# default.conf  (default server block)

Step 3: Configure an Upstream Block

The upstream directive defines a named group of backend servers. Although a single-server proxy can use proxy_pass with a direct address, using an upstream block is better practice because it makes future changes (adding more backends, adjusting weights) easy without touching the location block. Create a new configuration file:

sudo nano /etc/nginx/conf.d/nodeapp.conf

Add an upstream block at the top:

upstream nodeapp_backend {
    server 127.0.0.1:3000;
    keepalive 32;
}

The keepalive 32 directive keeps up to 32 idle connections open to the backend, reducing the overhead of repeatedly opening TCP connections for each request.

Step 4: Configure the Server Block with proxy_pass

Below the upstream block in the same file, add the server block:

server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;

    access_log  /var/log/nginx/nodeapp_access.log;
    error_log   /var/log/nginx/nodeapp_error.log;

    location / {
        proxy_pass         http://nodeapp_backend;
        proxy_http_version 1.1;

        # Pass the real client IP to the backend
        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;

        # Required for HTTP keepalive to the backend
        proxy_set_header   Connection        "";
    }
}

The four proxy_set_header directives are essential. Without them, your backend application sees all requests arriving from 127.0.0.1 rather than the real client IP, which breaks logging, rate limiting, and geolocation features.

Step 5: Tune Proxy Buffering Settings

Nginx can buffer backend responses before sending them to clients. For most applications this improves throughput, but it can introduce latency for streaming or real-time responses. Add buffering directives inside the location block:

location / {
    proxy_pass              http://nodeapp_backend;
    proxy_http_version      1.1;
    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_set_header        Connection        "";

    # Buffering
    proxy_buffering         on;
    proxy_buffer_size       4k;
    proxy_buffers           8 16k;
    proxy_busy_buffers_size 32k;

    # Timeouts
    proxy_connect_timeout   60s;
    proxy_send_timeout      60s;
    proxy_read_timeout      60s;

    # Pass errors from backend to client
    proxy_intercept_errors  off;
}

For WebSocket or Server-Sent Events applications, disable buffering and add the Upgrade header:

proxy_buffering         off;
proxy_set_header        Upgrade    $http_upgrade;
proxy_set_header        Connection "upgrade";

Step 6: Configure SELinux to Allow Network Connections

This step is the most common source of errors on RHEL 7. SELinux is enforcing by default and blocks Nginx from making outbound network connections to backend ports. You will see errors like connect() to 127.0.0.1:3000 failed (13: Permission denied) in /var/log/nginx/error.log.

Enable the httpd_can_network_connect boolean to allow Nginx (and Apache) to connect to any network port:

# Enable permanently (survives reboot)
sudo setsebool -P httpd_can_network_connect 1

# Verify
getsebool httpd_can_network_connect

If your backend runs on a non-standard port, you can alternatively label just that port instead of opening all network connections:

sudo semanage port -a -t http_port_t -p tcp 3000
sudo semanage port -l | grep http_port_t

The semanage command requires the policycoreutils-python package:

sudo yum install -y policycoreutils-python

Step 7: Test and Reload Nginx

Test the configuration syntax:

sudo nginx -t

Expected output:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Reload to apply changes without dropping existing connections:

sudo systemctl reload nginx

Step 8: Verify Proxy Headers Are Being Forwarded

Start a simple test server on port 3000 that prints all request headers (requires Node.js or Python):

# Python 3 one-liner that prints headers
python3 -m http.server 3000 &

# Or with Node.js
node -e "require('http').createServer((req,res)=>{
  console.log(req.headers); res.end('OK');
}).listen(3000);" &

Send a request through Nginx:

curl -I http://yourdomain.com/

Check that the backend receives x-real-ip, x-forwarded-for, and host headers matching your client IP and domain name.

Step 9: Add a Health Check Location (Optional)

Nginx open-source does not include active health checks (that feature is in Nginx Plus), but you can expose the built-in stub status page to monitor Nginx itself:

location /nginx_status {
    stub_status on;
    access_log  off;
    allow       127.0.0.1;
    deny        all;
}

Access it locally:

curl http://127.0.0.1/nginx_status

This returns active connections, accepted requests, and handled connections counts.

Configuring Nginx as a reverse proxy on RHEL 7 involves four key elements: an upstream block naming the backend, a server block with proxy_pass pointing at that upstream, correct proxy headers to preserve client identity, and the SELinux httpd_can_network_connect boolean to permit outbound connections. Once these pieces are in place you gain a robust, high-performance gateway in front of your application server that can later be extended with SSL termination, caching, rate limiting, and load balancing with minimal additional configuration.