Pro Tips — Apache vs Nginx Selection and Coexistence Patterns
This chapter revisits the real-world selection criteria for Apache vs Nginx, covers the port-separation pattern for running both web servers simultaneously on one server, and addresses common Apache issues in production.
Apache vs Nginx — Final Selection Criteria
| Criteria | Choose Apache | Choose Nginx |
|---|---|---|
| Traffic scale | Small to medium | Large scale (thousands of req/sec+) |
| PHP execution | mod_php (Prefork) or php-fpm | php-fpm (always) |
| Config flexibility | .htaccess, complex mod_rewrite | Simple config syntax |
| Module variety | Very diverse mod_* ecosystem | Limited (compiled in) |
| Memory usage | Higher (process/thread model) | Lower (event-driven) |
| Static files | Fast (sufficient) | Very fast |
| Legacy compatibility | Very high (.htaccess, etc.) | Low |
| Primary use case | PHP apps, CMS (WordPress·Drupal), legacy | Reverse proxy, high-traffic, containers |
Practical Conclusion
- New Java/API backend: Nginx (excellent reverse proxy)
- WordPress·Drupal·Joomla: Apache (mod_php, .htaccess dependency)
- Legacy PHP maintenance: Apache (keep existing .htaccess intact)
- Microservices API Gateway: Nginx
- Cloud / Kubernetes: Nginx Ingress
Apache + Nginx Coexistence (Port Separation Pattern)
When both Apache and Nginx need to run on the same server, use port separation to avoid conflicts.
Setup: Nginx Front-End + Apache Back-End
[Internet] → [Nginx :80/:443] → [Apache :8080] → [PHP/CMS]
Nginx handles SSL termination and static files; Apache focuses on PHP processing.
# /etc/apache2/ports.conf
Listen 8080
# /etc/apache2/sites-available/wordpress.conf
<VirtualHost *:8080>
ServerName example.com
DocumentRoot /var/www/wordpress
<Directory /var/www/wordpress>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Static files served directly by Nginx
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
root /var/www/wordpress;
expires 30d;
add_header Cache-Control "public";
}
# PHP processing proxied to Apache
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 $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Common Apache Issues and Solutions
Issue 1: mod_rewrite Not Working
Symptom: RewriteEngine On in .htaccess is ignored
Cause 1: AllowOverride None
<Directory "/var/www/html">
AllowOverride All # Fix: change to All
</Directory>
Cause 2: mod_rewrite not enabled
apache2ctl -M | grep rewrite
sudo a2enmod rewrite
sudo systemctl reload apache2
Issue 2: 403 Forbidden Error
Cause 1: File/directory permission issue
sudo find /var/www/html -type d -exec chmod 755 {} \;
sudo find /var/www/html -type f -exec chmod 644 {} \;
sudo chown -R www-data:www-data /var/www/html/
Cause 2: Require all denied in Directory block
<Directory "/var/www/html">
Require all granted # Change denied → granted
</Directory>
Cause 3: SELinux (CentOS/RHEL)
sudo restorecon -Rv /var/www/html/
Issue 3: Slow Response — HostnameLookups On
# Wrong — causes DNS lookup delays per request
HostnameLookups On
# Correct — always Off (default)
HostnameLookups Off
Issue 4: MaxRequestWorkers Exceeded
# Event MPM tuning
<IfModule mpm_event_module>
StartServers 4
MinSpareThreads 25
MaxSpareThreads 75
ThreadLimit 64
ThreadsPerChild 25
MaxRequestWorkers 400 # Increase this
MaxConnectionsPerChild 0
</IfModule>
Apache Security Hardening Checklist
ServerTokens Prod # Hide version info
ServerSignature Off # Hide server signature
<Directory /var/www/html>
Options -Indexes # Disable directory listing
</Directory>
FileETag MTime Size # Remove inode from ETag
<LimitExcept GET POST HEAD>
Require all denied # Block unnecessary HTTP methods
</LimitExcept>
<Files ".ht*">
Require all denied # Block .htaccess direct access
</Files>
<IfModule mod_headers.c>
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set X-XSS-Protection "1; mode=block"
</IfModule>
Deployment Automation Script
#!/bin/bash
set -e
echo "=== Apache Config Validation ==="
if apache2ctl configtest; then
echo "✅ Syntax check passed"
else
echo "❌ Syntax errors — aborting"
exit 1
fi
echo "=== Reloading Apache ==="
systemctl reload apache2
echo "✅ Deployment complete"
Summary
| Situation | Recommendation |
|---|---|
| New Java/API project | Choose Nginx |
| WordPress / PHP CMS | Choose Apache |
| Coexistence | Nginx (80/443) → Apache (8080) proxy |
| 403 error | Check permissions → AllowOverride → SELinux |
| Security hardening | ServerTokens Prod + Options -Indexes + security headers |
| Performance | Event MPM + HostnameLookups Off + AllowOverride None |