Updated: 28 September 2026 · Applies to: Nginx 1.20 to 1.28 and Apache 2.4 on AlmaLinux and Rocky Linux 9 and 10, RHEL 9 and 10, Ubuntu 24.04 and 26.04, Debian 13

In this setup Nginx answers all visitors on ports 80 and 443 and serves static files (images, CSS, JavaScript) itself, while Apache runs behind it on a local port and handles PHP and .htaccess rules. Sites keep working as they did on Apache, and Nginx takes the load of static files and many open connections. We tested this configuration with Nginx 1.20 and Apache 2.4.62 on AlmaLinux 9 and with Nginx 1.26 and Apache 2.4.68 on Debian 13: static files were served by Nginx alone, and Apache logged the visitor's real IP address.

On a cPanel server, do not set this up by hand: cPanel manages its web server configuration itself and has its own Nginx reverse proxy option (the ea-nginx package in EasyApache 4).

1. Move Apache to a local port

Make Apache listen on 127.0.0.1 port 8080 only:

  • AlmaLinux, Rocky Linux, RHEL: in /etc/httpd/conf/httpd.conf change Listen 80 to Listen 127.0.0.1:8080. If mod_ssl is installed, remove the Listen 443 line in /etc/httpd/conf.d/ssl.conf, because Nginx will use port 443.
  • Ubuntu, Debian: in /etc/apache2/ports.conf use Listen 127.0.0.1:8080 and remove the Listen 443 lines.

Change each site's <VirtualHost *:80> to <VirtualHost 127.0.0.1:8080>:

<VirtualHost 127.0.0.1:8080>
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /var/www/example.com/public
    <Directory /var/www/example.com/public>
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

2. Let Apache see the visitor's IP address

Behind a proxy, Apache sees every request coming from 127.0.0.1. mod_remoteip restores the real address from the header Nginx adds. Create /etc/httpd/conf.d/remoteip.conf (on Ubuntu and Debian: /etc/apache2/conf-available/remoteip.conf, then a2enmod remoteip and a2enconf remoteip):

RemoteIPHeader X-Forwarded-For
RemoteIPInternalProxy 127.0.0.1
LogFormat "%a %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined

The LogFormat line writes the restored address (%a) to the access log instead of the proxy's address.

3. Configure Nginx

Install Nginx (dnf install nginx or apt install nginx) and create /etc/nginx/conf.d/example.com.conf:

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    root /var/www/example.com/public;

    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;

    # Everything goes to Apache by default
    location / {
        proxy_pass http://127.0.0.1:8080;
    }

    # Nginx serves existing static files itself
    location ~* \.(css|js|jpe?g|png|gif|svg|webp|ico|woff2?)$ {
        expires 30d;
        try_files $uri @apache;
    }

    location @apache {
        proxy_pass http://127.0.0.1:8080;
    }
}

A static file that does not exist on disk is passed to Apache, so rewrites that generate such URLs keep working. On Ubuntu and Debian, also remove the default site: rm /etc/nginx/sites-enabled/default.

4. SELinux (AlmaLinux, Rocky Linux, RHEL)

Apache may listen on port 8080 without changes, but Nginx needs permission to connect to it:

setsebool -P httpd_can_network_relay 1

5. Test and start

apachectl configtest && nginx -t
systemctl restart httpd     # apache2 on Ubuntu and Debian
systemctl enable --now nginx

Check that a page loads, that curl -I http://example.com/style.css shows Server: nginx with an Expires header, and that /var/log/httpd/access_log (or /var/log/apache2/access.log) shows visitors' addresses, not 127.0.0.1.

HTTPS

Get the certificate for Nginx, which now answers the visitors, for example with certbot --nginx -d example.com -d www.example.com. Apache keeps serving plain HTTP on the local port; X-Forwarded-Proto tells applications that the visitor used HTTPS. Some applications need an extra setting to trust this header (in WordPress, for example, a line in wp-config.php), otherwise they may redirect in a loop.

Things to know

  • Nginx does not read .htaccess. Rules there that protect static files (for example a deny on a folder of images) are bypassed for the file types Nginx serves itself. Protect such folders in the Nginx configuration as well, or leave those file types to Apache.
  • If you want to drop Apache later, Nginx can run PHP directly with PHP-FPM, but then all .htaccess rules must be rewritten for Nginx.

Official documentation: Nginx proxy module and Apache mod_remoteip.

Ucartz services for this topic

Was this answer helpful? 0 Users Found This Useful (0 Votes)