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.confchangeListen 80toListen 127.0.0.1:8080. Ifmod_sslis installed, remove theListen 443line in/etc/httpd/conf.d/ssl.conf, because Nginx will use port 443. - Ubuntu, Debian: in
/etc/apache2/ports.confuseListen 127.0.0.1:8080and remove theListen 443lines.
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
.htaccessrules must be rewritten for Nginx.
Official documentation: Nginx proxy module and Apache mod_remoteip.