Nginx and Apache can each serve a website on their own. They can also work together, but they cannot both own the same listening address and port. Installing both with untouched defaults often leads to an “address already in use” error rather than a faster website.
The examples below target an ordinary Ubuntu 24.04 server with distribution packages. The combined setup is for a fresh test host with no existing websites. Do not replace production virtual hosts with these examples. Back up configurations, keep a working administrative session and plan any service interruption before changing listeners.
Install one server first
Inspect current listeners before installing anything:
sudo ss -ltnp
sudo apt update
For an Apache-only host, use:
sudo apt install apache2
sudo apache2ctl configtest
sudo systemctl status apache2 --no-pager
curl -I http://127.0.0.1/
For a separate Nginx-only host, use this alternative instead:
sudo apt install nginx
sudo nginx -t
sudo systemctl status nginx --no-pager
curl -I http://127.0.0.1/
Ubuntu's Apache installation guide and Nginx installation guide explain the package layouts. Installation can start the service automatically. If a port is already occupied, identify its owner rather than repeatedly restarting both services.
Optional: Nginx on port 80, Apache on loopback port 8080
For this fresh-host HTTP demonstration, start with Apache installed and move it off port 80 before installing Nginx. In /etc/apache2/ports.conf, replace the active Listen 80 line with:
Listen 127.0.0.1:8080
Replace the default virtual-host block in /etc/apache2/sites-available/000-default.conf with the following. This assumes the stock Apache configuration and its existing access permissions for /var/www/html. Do not enable other sites or SSL listeners for this small demonstration.
<VirtualHost 127.0.0.1:8080>
ServerName example.test
DocumentRoot /var/www/html
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
A virtual-host address does not create a listener by itself: the Listen setting must agree with it. Apache documents this distinction in its address and port binding guide. Test first, then restart Apache to apply this listener change:
sudo apache2ctl configtest && sudo systemctl restart apache2
sudo ss -ltnp
curl -I -H "Host: example.test" http://127.0.0.1:8080/
Confirm that Apache now listens only on 127.0.0.1:8080 for this demo, and that port 80 is free. Install Nginx, then replace the default server block in /etc/nginx/sites-available/default with:
server {
listen 80 default_server;
server_name example.test;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
This assumes the package's default site is enabled and there is no second default server on port 80. Overwriting the forwarded-for header is intentional for this single trusted edge proxy; do not copy the trust assumptions unchanged into a multi-proxy deployment. The backend application still needs explicit trusted-proxy configuration if it uses these headers.
sudo nginx -t && sudo systemctl reload nginx
curl -I -H "Host: example.test" http://127.0.0.1/
Nginx's beginner guide explains server blocks and proxying. The server name here is an example, not a public domain you should resolve. Local curl requests supply it as a Host header.
Before exposing the site
This configuration is HTTP-only and does not protect credentials in transit. Add a real domain and correctly configured TLS before handling logins or sensitive data. If using a firewall, preserve SSH access and allow only the intended public web ports; do not expose backend port 8080. A cloud firewall may also need a corresponding rule.
Apache's concurrency model depends on its selected MPM; it is not always one process per request. PHP also requires a configured handler, commonly PHP-FPM, rather than appearing automatically after installing a web server. See Apache's MPM documentation. Measure the actual workload before changing worker limits.
If the front end returns 502
Test the loopback backend directly, inspect the Apache service status and then read Nginx's error log. A successful configuration test validates syntax, not application health. If the backend request works but the proxy fails, compare the exact address and port in proxy_pass. Keep the previous working configuration available so a failed change can be reversed.