Comprehensive Guide for Setting Up Apache Web Server on Linux

The Apache HTTP Server is a fast, open-source web server that allows you to quickly set up a web server anywhere. It delivers static files like images, JavaScript etc., and serves dynamic apps through PHP-FPM, Python WSGI, Node.js, or other backends via reverse proxy. It also handles TLS termination, HTTP/2, URL rewriting, caching. Admins use it for single sites, multi-tenant hosting, and as an edge proxy in front of application servers.
This guide shows how to install and run Apache on modern Linux distributions. It covers installation, service management, default directory layout, and virtual host setup. We will guide you through how to enable HTTPS, configure HTTP/2, and learn about HTTP/3. This article also covers PHP with PHP-FPM, reverse proxy patterns, performance tuning, caching, security hardening of your webserver, firewalls, port forwarding, monitoring, and troubleshooting.
TLDR
- Install
apache2on Debian and Ubuntu,httpdelsewhere. - Use MPM event, PHP-FPM via
proxy_fcgi, HTTP/2 on by default where available, HTTP/3 still experimental for Apache. - Use Let’s Encrypt with certbot. Auto renewal runs via systemd timer.
- Configure TLS to TLS 1.2 and 1.3 and add security headers.
- Open only 80 and 443 in your firewall.
- Verify every step with
systemctl,apachectl -t, andcurl.
What you'll need
- Root or sudo access.
- 64-bit Linux with package manager access and time sync enabled.
- DNS A/AAAA records pointing to your webserver for each hostname you’ll setup.
- Open TCP 80 and 443 (secure port) at the host firewall and any firewall.
- For TLS 1.3 you need Apache 2.4.43+ with OpenSSL 1.1.1 or newer.
- Minimum for small sites: 1 vCPU, 1-2 GB RAM, 10 GB free disk. Plan extra space for logs and caches.
Quick checks
apachectl -v
openssl version
hostname -I
dig +short A example.com # or AAAA for IPv6Supported Linux distributions and package names
- Debian and Ubuntu: package
apache2, serviceapache2. Modules viaa2enmod. - RHEL, AlmaLinux, Rocky, CentOS Stream,
Fedora, Amazon Linux, openSUSE, Arch: package
httpd, servicehttpd. Modules inconf.modules.d.
The default document root directory for openSUSE is /srv/www/htdocs, others use /var/www/html.
Typical module package names you may install when needed
- HTTP/2:
libapache2-mod-http2(Debian/Ubuntu),mod_http2(RHEL/Fedora/openSUSE). - Brotli:
libapache2-mod-brotliormod_brotlidepending on your Linux distribution. - Certbot:
snapd+certbotsnap (Ubuntu), orcertbotfrom your Linux distribution repository. - PHP-FPM:
php-fpm(RHEL/Fedora/openSUSE/Arch), or version-specific packages likephp8.3-fpm(Debian/Ubuntu).
Install Apache on each of your Linux distribution
Debian / Ubuntu (LTS)
sudo apt update
sudo apt install -y apache2
sudo systemctl enable --now apache2Verify
systemctl status apache2 --no-pager
apachectl -v
curl -I http://localhostRHEL 8/9, AlmaLinux, Rocky, CentOS Stream
sudo dnf install -y httpd
sudo systemctl enable --now httpdVerify
systemctl status httpd --no-pager
httpd -v
curl -I http://localhostFedora
sudo dnf install -y httpd
sudo systemctl enable --now httpdVerify
systemctl status httpd --no-pager
httpd -v
curl -I http://localhostopenSUSE Leap/Tumbleweed
sudo zypper install -y apache2
sudo systemctl enable --now apache2Verify
systemctl status apache2 --no-pager
curl -I http://localhostArch Linux
sudo pacman -Syu --noconfirm apache
sudo systemctl enable --now httpdVerify
systemctl status httpd --no-pager
curl -I http://localhostAmazon Linux 2 / 2023
# AL2
sudo yum install -y httpd
# AL2023
sudo dnf install -y httpd
sudo systemctl enable --now httpdVerify
systemctl status httpd --no-pager
curl -I http://localhostBasic service management
Common commands
# Start/stop/restart
sudo systemctl start apache2 # Debian/Ubuntu
sudo systemctl stop apache2 # Debian/Ubuntu
sudo systemctl restart apache2 # Debian/Ubuntu
sudo systemctl start httpd # RHEL/Fedora/others
sudo systemctl stop httpd # RHEL/Fedora/others
sudo systemctl restart httpd # RHEL/Fedora/others
# Reload config without dropping connections
sudo systemctl reload apache2 # Debian/Ubuntu
sudo systemctl reload httpd # RHEL/Fedora/others
# Graceful restart (finishes active requests)
sudo apachectl -k graceful
# View service logs
sudo journalctl -u apache2 -f # Debian/Ubuntu (follow mode)
sudo journalctl -u httpd -f # RHEL/Fedora/others (follow mode)
sudo journalctl -u apache2 -e --no-pager # Debian/Ubuntu (jump to end)
sudo journalctl -u httpd -e --no-pager # RHEL/Fedora/others (jump to end)
# Additional useful commands:
sudo systemctl enable apache2 # Enable auto-start (Debian/Ubuntu)
sudo systemctl enable httpd # Enable auto-start (RHEL/Fedora/others)
sudo systemctl status apache2 # Check service status (Debian/Ubuntu)
sudo systemctl status httpd # Check service status (RHEL/Fedora/others)
sudo apachectl configtest # Check configuration syntaxReload vs Restart vs Graceful
Understanding the difference between these operations is crucial for maintaining service availability during configuration changes and updates.
systemctl reload apache2,systemctl reload httpd- Safely rereads configuration files without dropping existing connections. Ideal for most configuration changes that don't require module changes.systemctl restart apache2,systemctl restart httpd- Stops and starts the daemon, dropping all active connections. Use when changing MPM modules, adding/removing modules, or when reload doesn't work.apachectl -k graceful- Finishes serving active requests before applying new configurations. Most production-friendly option for configuration updates.
| Command | Active Connections | Downtime | Use Case |
|---|---|---|---|
systemctl reload | Preserved | None | Config file changes |
apachectl graceful | Completed gracefully | None | Production config updates |
systemctl restart | Dropped | Brief | Module changes, major updates |
Verification Commands
# Check configuration syntax first
sudo apachectl -t
# Verify service state
sudo systemctl show -p SubState apache2 # Debian/Ubuntu
sudo systemctl show -p SubState httpd # RHEL/Fedora/others
# Check recent logs for errors
sudo journalctl -u apache2 -n 20 --no-pager # Debian/Ubuntu
sudo journalctl -u httpd -n 20 --no-pager # RHEL/Fedora/others
# Monitor active connections during changes
sudo ss -tulpn | grep :80
sudo ss -tulpn | grep :443Configuration Testing and Validation
Always test configuration changes before applying them to production systems to prevent service disruption and identify syntax errors early.
# Basic syntax check (essential before any reload/restart)
sudo apachectl -t
# Alternative syntax (same function)
sudo apachectl configtest
# Detailed virtual host analysis
sudo apachectl -t -D DUMP_VHOSTS
# Show parsed configuration (including all included files)
sudo apachectl -t -D DUMP_RUN_CFG
# List all loaded modules
sudo apachectl -M
# Show current virtual host configuration
sudo apachectl -SDirectory layout and key config files
- Debian/Ubuntu:
/etc/apache2/withsites-available,sites-enabled,mods-available,mods-enabled,ports.conf, andenvvars. Logs in/var/log/apache2/. Default document root/var/www/html. - RHEL/Fedora/Alma/Rocky/CentOS/Amazon/openSUSE/Arch:
/etc/httpd/conf/httpd.conf,conf.d/*.conf,conf.modules.d/*.conf. Logs in/var/log/httpd/. Default document root/var/www/htmlexcept openSUSE/srv/www/htdocs.
Common paths by Linux distribution
| Linux distribution | Config root | Vhost locations | DocumentRoot | Logs |
|---|---|---|---|---|
| Debian/Ubuntu | /etc/apache2/ | sites-available/, sites-enabled/ | /var/www/html | /var/log/apache2/ |
| RHEL/Alma/Rocky/Fedora/Amazon/Arch | /etc/httpd/ | conf.d/*.conf | /var/www/html | /var/log/httpd/ |
| openSUSE Leap/TW | /etc/apache2/ | vhosts.d/ (and conf.d/) | /srv/www/htdocs | /var/log/apache2/ |
Tips Enable a site on Debian/Ubuntu with a2ensite, enable a module with
a2enmod, then systemctl reload apache2.
Verify loaded vhosts and modules
sudo apachectl -S
sudo apachectl -MVirtual hosts, name-based and IP-based
Modern Apache does not need NameVirtualHost. Use VirtualHost with *:80 and
*:443.
HTTP vhost that redirects to HTTPS
/etc/apache2/sites-available/example-http.conf or /etc/httpd/conf.d/example-http.conf:
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example
# Redirect all HTTP to HTTPS
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
ErrorLog ${APACHE_LOG_DIR}/example_error.log
CustomLog ${APACHE_LOG_DIR}/example_access.log combined
</VirtualHost>Enable on Debian/Ubuntu
sudo a2enmod rewrite
sudo a2ensite example-http
sudo apachectl -t && sudo systemctl reload apache2Global ServerName and IP-based vhost
# Avoid 'Could not reliably determine the server's fully qualified domain name' warning
ServerName example.comIP-based vhost when you bind different addresses:
Listen 192.0.2.10:80
<VirtualHost 192.0.2.10:80>
ServerName app.example.com
DocumentRoot /var/www/app
ErrorLog ${APACHE_LOG_DIR}/example_error.log
CustomLog ${APACHE_LOG_DIR}/example_access.log combined
</VirtualHost>Verify
curl -I --resolve app.example.com:80:192.0.2.10 http://app.example.com/HTTPS vhost with separate logs
<VirtualHost *:443>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example
<Directory "/var/www/example">
Options -Indexes -Includes -ExecCGI
AllowOverride None
Require all granted
</Directory>
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
# Strong TLS
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
TLSCipherSuite TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256
SSLHonorCipherOrder off
SSLUseStapling on
SSLStaplingCache shmcb:/var/run/ocsp(128000)
Protocols h2 http/1.1
ErrorLog ${APACHE_LOG_DIR}/example_ssl_error.log
CustomLog ${APACHE_LOG_DIR}/example_ssl_access.log combined
</VirtualHost>HTTPS with Let’s Encrypt and automatic renewal
Install certbot
Ubuntu (snap) for RHEL/Fedora/openSUSE/Arch/Amazon: install certbot from repos if available:
sudo snap install core && sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbotObtain a certificate
Apache plugin where supported:
sudo certbot --apache -d example.com -d www.example.comWebroot method (works everywhere):
sudo certbot certonly --webroot -w /var/www/example -d example.com -d www.example.com
# Reload Apache after first issuance
sudo systemctl reload apache2|httpdPost-issuance checks and common gotchas
- Open port 80 during issuance for HTTP-01 challenges.
- Set correct webroot path for each vhost when using
--webroot. - Reload Apache after obtaining or renewing certificates.
Verify certificate and OCSP stapling
openssl s_client -connect example.com:443 -servername example.com -status | grep -E "Protocol|Cipher|issuer|Verify return code|OCSP"Check timer
systemctl list-timers 'snap.certbot*' 'certbot*'
sudo certbot renew --dry-runAuto renewal
Certbot packages set a systemd timer or cron to renew before expiry. Test renewal:
sudo certbot renew --dry-run
systemctl list-timers | grep -i certbotThe snap-based certbot installs a systemd timer that runs twice daily.
HTTP/2 status and configuration, and current status of HTTP/3 for Apache
- HTTP/2: enable mod_http2 and add Protocols h2 http/1.1 on TLS vhosts. Use ALPN. Verify:
curl -I --http2 https://example.com. - Apache httpd has no officially shipped HTTP/3 module as of 2.4.65. Use a reverse proxy with HTTP/3 support, for example Nginx 1.25+, Caddy, or a CDN. of 2025. Many Linux distributions ship without mod_http3. Consider terminating HTTP/3 at a reverse proxy like NGINX 1.25+ or a CDN.
Enable HTTP/2 by Linux distribution
- Debian/Ubuntu:
sudo a2enmod http2 && sudo systemctl reload apache2 - RHEL/Alma/Rocky/Fedora: ensure
LoadModule http2_module modules/mod_http2.soinconf.modules.d/, then reload. - openSUSE:
sudo a2enmod http2, reload.
Verify ALPN
curl -I --http2 https://example.com
openssl s_client -alpn h2 -connect example.com:443 -servername example.com | grep -i alpnApache httpd has no officially shipped HTTP/3 module as of 2.4.65. Use a reverse proxy with HTTP/3 support, for example Nginx 1.25+, Caddy, or a CDN. Use a proxy or CDN for QUIC if required.
PHP Integration Using PHP-FPM via proxy_fcgi
Using PHP-FPM with Apache's MPM event provides better performance, resource isolation, and scalability compared to mod_php. This configuration allows Apache to serve static content efficiently while delegating PHP processing to dedicated PHP-FPM processes.
Enable Modules and Configure PHP-FPM
Debian/Ubuntu
# Install PHP-FPM (specify version - 8.1, 8.2, 8.3, etc.)
sudo apt update
sudo apt install -y php8.3-fpm
# Enable required Apache modules
sudo a2enmod proxy proxy_fcgi setenvif
sudo a2dismod mpm_prefork # Disable prefork if using mod_php
sudo a2enmod mpm_event # Enable event MPM for better performance
# Create PHP-FPM configuration
sudo tee /etc/apache2/conf-available/php8.3-fpm.conf >/dev/null <<'EOF'
# Handle PHP files via PHP-FPM
<FilesMatch "\.php$">
SetHandler "proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost/"
</FilesMatch>
# Directory index preferences
DirectoryIndex index.php index.html
# Security: Prevent direct access to sensitive files
<Files "\.(env|ini|log|sh|sql|tpl|twig|yml)$">
Require all denied
</Files>
EOF
# Enable PHP-FPM configuration
sudo a2enconf php8.3-fpm
# Start and enable PHP-FPM service
sudo systemctl enable php8.3-fpm
sudo systemctl start php8.3-fpm
# Test configuration and restart Apache
sudo apachectl -t && sudo systemctl restart apache2RHEL 8/9, AlmaLinux, Rocky, CentOS Stream, Fedora
# Install PHP-FPM
sudo dnf install -y php-fpm
# Enable required modules (ensure these are uncommented in httpd.conf)
# LoadModule proxy_module modules/mod_proxy.so
# LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
# Enable and start PHP-FPM service
sudo systemctl enable php-fpm
sudo systemctl start php-fpm
# Configure Apache to use PHP-FPM (add to /etc/httpd/conf.d/php-fpm.conf)
sudo tee /etc/httpd/conf.d/php-fpm.conf >/dev/null <<'EOF'
# Handle PHP files via PHP-FPM
<FilesMatch "\.php$">
SetHandler "proxy:unix:/run/php-fpm/www.sock|fcgi://localhost/"
</FilesMatch>
# Directory index preferences
DirectoryIndex index.php index.html
# Security: Prevent direct access to sensitive files
<Files "\.(env|ini|log|sh|sql|tpl|twig|yml)$">
Require all denied
</Files>
EOF
# Test configuration and restart Apache
sudo apachectl -t && sudo systemctl restart httpdopenSUSE Leap/Tumbleweed
# Install PHP-FPM
sudo zypper install -y php8-fpm
# Enable and start PHP-FPM service
sudo systemctl enable php8-fpm
sudo systemctl start php8-fpm
# Configure Apache (add to /etc/apache2/conf.d/php-fpm.conf)
sudo tee /etc/apache2/conf.d/php-fpm.conf >/dev/null <<'EOF'
# Handle PHP files via PHP-FPM
<FilesMatch "\.php$">
SetHandler "proxy:unix:/run/php8-fpm.sock|fcgi://localhost/"
</FilesMatch>
DirectoryIndex index.php index.html
EOF
# Enable required modules
sudo a2enmod proxy
sudo a2enmod proxy_fcgi
# Test configuration and restart Apache
sudo apachectl -t && sudo systemctl restart apache2Arch Linux
# Install PHP-FPM
sudo pacman -Syu --noconfirm php php-fpm
# Enable and start PHP-FPM service
sudo systemctl enable php-fpm
sudo systemctl start php-fpm
# Configure Apache (add to /etc/httpd/conf/extra/php-fpm.conf)
sudo tee /etc/httpd/conf/extra/php-fpm.conf >/dev/null <<'EOF'
# Handle PHP files via PHP-FPM
<FilesMatch "\.php$">
SetHandler "proxy:unix:/run/php-fpm/php-fpm.sock|fcgi://localhost/"
</FilesMatch>
DirectoryIndex index.php index.html
EOF
# Include the configuration in httpd.conf
echo "Include conf/extra/php-fpm.conf" | sudo tee -a /etc/httpd/conf/httpd.conf
# Test configuration and restart Apache
sudo apachectl -t && sudo systemctl restart httpdVirtualHost Configuration Example
<VirtualHost *:80>
ServerName example.com
DocumentRoot /var/www/example.com/public_html
# Security: Protect the directory
<Directory "/var/www/example.com/public_html">
Options -Indexes -Includes -ExecCGI
AllowOverride All
Require all granted
# Handle PHP files via PHP-FPM
<FilesMatch "\.php$">
SetHandler "proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost/"
</FilesMatch>
# Security: Prevent access to sensitive files
<Files "\.(env|ini|log|sh|sql|tpl|twig|yml)$">
Require all denied
</Files>
</Directory>
# Directory index preferences
DirectoryIndex index.php index.html
# Logging
ErrorLog /var/log/apache2/example_com_error.log
CustomLog /var/log/apache2/example_com_access.log combined
</VirtualHost>Security: SELinux and AppArmor Configuration
SELinux (RHEL/Fedora/CentOS)
# Check SELinux status
sudo sestatus
# Allow Apache to connect to network and sockets
sudo setsebool -P httpd_can_network_connect on
sudo setsebool -P httpd_execmem on
sudo setsebool -P httpd_unified on
# Label custom ports if using TCP instead of sockets
sudo semanage port -a -t http_port_t -p tcp 9000
# Check for denials and troubleshoot
sudo ausearch -m avc -ts recent
sudo sealert -a /var/log/audit/audit.logAppArmor (Ubuntu/Debian)
# Check AppArmor status
sudo aa-status
# If using custom socket paths or experiencing permission issues:
sudo aa-complain apache2 # Temporarily put in complain mode
# Test your configuration, then re-enforce:
sudo aa-enforce apache2
# View and modify Apache AppArmor profile if needed
sudo cat /etc/apparmor.d/usr.sbin.apache2PHP-FPM Pool Tuning and Optimization
Optimize PHP-FPM pool settings based on your server's available memory and expected traffic. Edit the pool configuration file for your distribution:
Pool Configuration Locations
- Debian/Ubuntu:
/etc/php/8.3/fpm/pool.d/www.conf - RHEL/Fedora/CentOS:
/etc/php-fpm.d/www.conf - openSUSE:
/etc/php8/fpm/pool.d/www.conf - Arch Linux:
/etc/php/php-fpm.d/www.conf
Optimized Pool Settings
; Process manager settings
pm = dynamic
; Maximum number of child processes
pm.max_children = 30
; Number of child processes created on startup
pm.start_servers = 4
; Minimum number of idle server processes
pm.min_spare_servers = 2
; Maximum number of idle server processes
pm.max_spare_servers = 8
; Maximum requests per child before respawn
pm.max_requests = 500
; Use Unix socket for better performance
listen = /run/php/php8.3-fpm.sock
; Socket permissions
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
; Security and performance
; Prevent slow requests from blocking the pool
request_terminate_timeout = 60s
request_slowlog_timeout = 30s
; Process ownership
user = www-data
group = www-data
; Process priority (lower number = higher priority)
process.priority = -10Verification and Troubleshooting
Verification Steps
# 1. Check PHP-FPM service status
sudo systemctl status php8.3-fpm # Debian/Ubuntu
sudo systemctl status php-fpm # RHEL/Fedora
# 2. Verify socket file exists and has correct permissions
ls -la /run/php/php8.3-fpm.sock # Debian/Ubuntu
ls -la /run/php-fpm/www.sock # RHEL/Fedora
# 3. Test PHP processing with a simple script
sudo tee /var/www/html/phpinfo.php >/dev/null <<'EOF'
<?php
header('Content-Type: text/plain');
echo "PHP is working via FPM\n";
echo "Server: " . $_SERVER['SERVER_SOFTWARE'] . "\n";
echo "PHP Version: " . phpversion() . "\n";
?>
EOF
# 4. Test the PHP script
curl http://localhost/phpinfo.php
# 5. Check Apache error logs for PHP-FPM issues
sudo tail -f /var/log/apache2/error.log
sudo tail -f /var/log/httpd/error_log
# 6. Check PHP-FPM logs
sudo tail -f /var/log/php8.3-fpm.log # Debian/Ubuntu
sudo tail -f /var/log/php-fpm/www-error.log # RHEL/Fedora
# 7. Remove test file after verification
sudo rm /var/www/html/phpinfo.phpCommon Issues and Solutions
| Issue | Symptom | Solution |
|---|---|---|
| Socket permissions | 502 Bad Gateway | Check socket file exists and www-data/apache user has access |
| SELinux blocking | Permission denied in logs | Use setsebool and check audit logs |
| Wrong MPM | Apache won't start | Use MPM event, not prefork with PHP-FPM |
| PHP memory limits | White screen or errors | Increase memory_limit in php.ini |
| Missing modules | Apache configtest fails | Enable proxy, proxy_fcgi modules |
Performance Monitoring
# Monitor PHP-FPM processes
sudo ps aux | grep php-fpm
sudo systemctl status php8.3-fpm
# Check PHP-FPM pool status (enable status page in pool config)
# Add to www.conf: pm.status_path = /status
curl http://localhost/status?full
# Monitor Apache and PHP-FPM resource usage
sudo htop
sudo apt install atop && sudo atop # More detailed monitoring
# Check for slow PHP requests
sudo tail -f /var/log/php8.3-fpm-slow.logReverse Proxy Configuration: Basics, Health Checks, and Timeouts
Apache's reverse proxy capabilities allow you to forward requests to backend application servers, load balance between multiple servers, and provide health monitoring for high availability.
Enable Proxy Modules
Debian/Ubuntu
# Enable core proxy modules
sudo a2enmod proxy proxy_http proxy_balancer lbmethod_byrequests headers
sudo a2enmod proxy_hcheck # Health checks
sudo a2enmod proxy_wstunnel # WebSocket support
sudo a2enmod slotmem_shm # Required for balancer
sudo a2enmod remoteip # Client IP forwarding
# Test configuration and reload
sudo apachectl -t && sudo systemctl reload apache2RHEL/Fedora/CentOS
# Ensure these modules are loaded in /etc/httpd/conf.modules.d/00-proxy.conf
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so
LoadModule headers_module modules/mod_headers.so
LoadModule proxy_hcheck_module modules/mod_proxy_hcheck.so
LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so
LoadModule slotmem_shm_module modules/mod_slotmem_shm.so
LoadModule remoteip_module modules/mod_remoteip.so
# Restart Apache to load modules
sudo systemctl restart httpdBasic Reverse Proxy Configuration
# Forward all requests to backend application
ProxyPass "/" "http://127.0.0.1:8080/"
ProxyPassReverse "/" "http://127.0.0.1:8080/"
# Preserve original host header
ProxyPreserveHost On
# Timeout settings (in seconds)
ProxyTimeout 60
# Connection settings
ProxyRequests Off # Important: disable forward proxy for securityPath-Based Routing Example
# Proxy specific paths to different backends
ProxyPass "/api/" "http://127.0.0.1:3000/"
ProxyPassReverse "/api/" "http://127.0.0.1:3000/"
ProxyPass "/app/" "http://127.0.0.1:8080/"
ProxyPassReverse "/app/" "http://127.0.0.1:8080/"
# Exclude static assets from proxying
ProxyPass "/static/" "!"
Alias "/static/" "/var/www/static/"Load Balancer with Health Checks
Basic Load Balancer
# Define balancer cluster with health checks
<Proxy "balancer://appcluster">
BalancerMember "http://10.0.0.11:8080" route=1
BalancerMember "http://10.0.0.12:8080" route=2
BalancerMember "http://10.0.0.13:8080" route=3 status=+H
ProxySet lbmethod=byrequests
</Proxy>
# Use the balancer
ProxyPass "/" "balancer://appcluster/"
ProxyPassReverse "/" "balancer://appcluster/"Advanced Health Checks with mod_proxy_hcheck
# Health check configuration
<Proxy "balancer://webapp">
BalancerMember "http://10.0.0.11:8080" hcheck=on hcinterval=10 hcpref=pass1
BalancerMember "http://10.0.0.12:8080" hcheck=on hcinterval=10 hcpref=pass2
# Health check settings
ProxyHCExpr ok234 {%{REQUEST_STATUS} =~ /^[234]/}
ProxyHCExpr ok200 {%{REQUEST_STATUS} == 200}
# Health check for member 1
ProxyHCExpr pass1 %{REQUEST_STATUS} == 200
# Health check for member 2 - check specific health endpoint
ProxyHCExpr pass2 %{REQUEST_STATUS} == 200
</Proxy>
# Alternative: Simple health check configuration
<Proxy "balancer://simpleapp">
BalancerMember "http://10.0.0.11:8080" hcheck=on hcmethod=GET hcuri=/health hcinterval=30
BalancerMember "http://10.0.0.12:8080" hcheck=on hcmethod=GET hcuri=/health hcinterval=30
ProxySet hcmethod=GET hcuri=/health hcinterval=30
</Proxy>
ProxyPass "/" "balancer://webapp/"
ProxyPassReverse "/" "balancer://webapp/"Balancer Manager for Monitoring
# Enable balancer manager for monitoring (restrict access!)
<Location "/balancer-manager">
SetHandler balancer-manager
Require ip 10.0.0.0/8
Require local
</Location>Client IP Forwarding and Headers
mod_remoteip Configuration
# Load module (if not already loaded)
LoadModule remoteip_module modules/mod_remoteip.so
# Configure trusted proxies (load balancers, CDNs, etc.)
RemoteIPHeader X-Forwarded-For
RemoteIPInternalProxy 10.0.0.0/8
RemoteIPInternalProxy 172.16.0.0/12
RemoteIPInternalProxy 192.168.0.0/16
RemoteIPTrustedProxy 203.0.113.45 # Your load balancer IP
RemoteIPTrustedProxy 2001:db8::1 # IPv6 example
# Update log format to use client IP
LogFormat "%a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combinedForwarded Headers Configuration
# Preserve original client information
RequestHeader set X-Forwarded-Proto "https" env=HTTPS
RequestHeader set X-Forwarded-Host "%{HTTP_HOST}s"
Note: %{...}s pulls server/request vars via mod_headers format string. Prefer X-Forwarded-Proto and X-Forwarded-For for scheme and client IP in apps.
RequestHeader set X-Forwarded-Server "%{SERVER_NAME}s"
RequestHeader set X-Real-IP "%{REMOTE_ADDR}s"
# Trust client IPs only from defined proxies via mod_remoteip (see config above)
# Do not unset X-Forwarded-For or X-Real-IP here.Timeouts and Performance Tuning
Timeout Configuration
# Global proxy timeout settings
ProxyTimeout 300
# Per-route timeout configuration
ProxyPass "/api/" "http://127.0.0.1:3000/" timeout=300 retry=0
ProxyPass "/app/" "http://127.0.0.1:8080/" timeout=60 retry=3
# Connection pool settings
ProxyPass "/" "http://127.0.0.1:8080/" retry=0 timeout=60 connectiontimeout=5
# Buffer and packet settings
ProxyIOBufferSize 65536WebSocket Proxy Configuration
# WebSocket proxy with proper headers
ProxyPass "/ws/" "ws://127.0.0.1:9000/"
ProxyPassReverse "/ws/" "ws://127.0.0.1:9000/"
# Alternative: Rewrite for WebSocket upgrade
RewriteEngine On
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule "^/ws/(.*)" "ws://127.0.0.1:9000/$1" [P,L]
# Set WebSocket specific headers
RequestHeader set Upgrade "websocket"
RequestHeader set Connection "Upgrade"Security Configuration
Proxy Security Headers
# Critical security settings
ProxyRequests Off # Prevent open proxy
ProxyVia Off # Don't add Via header
# For reverse proxying, a <Proxy> block is not required. Do NOT enable a forward proxy.
# If you intentionally run a forward proxy, lock it down by IP:
# <Proxy "*">
# Require ip 10.0.0.0/8
# </Proxy>
# Headers security
Header always unset X-Powered-ByCookie and Domain Rewriting
# Fix cookie domains and paths
ProxyPassReverseCookieDomain internal.local example.com
ProxyPassReverseCookiePath / /app/
# Additional response header rewriting
ProxyPassReverse "http://127.0.0.1:8080/" "https://example.com/"
ProxyPassReverse "http://backend.internal/" "https://example.com/"Complete VirtualHost Example
<VirtualHost *:443>
ServerName example.com
DocumentRoot /var/www/html
# SSL configuration here...
# Enable modules for this VirtualHost
ProxyRequests Off
ProxyPreserveHost On
ProxyTimeout 300
# Client IP forwarding
RemoteIPHeader X-Forwarded-For
RemoteIPInternalProxy 10.0.0.0/8
# Forwarded headers
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-Host "%{HTTP_HOST}s"
# Load balancer with health checks
<Proxy "balancer://appcluster">
BalancerMember "http://10.0.0.11:8080" hcheck=on hcinterval=30
BalancerMember "http://10.0.0.12:8080" hcheck=on hcinterval=30
ProxySet lbmethod=byrequests
</Proxy>
# Proxy configuration
ProxyPass "/" "balancer://appcluster/"
ProxyPassReverse "/" "balancer://appcluster/"
# WebSocket endpoint
ProxyPass "/ws/" "ws://10.0.0.13:9000/"
ProxyPassReverse "/ws/" "ws://10.0.0.13:9000/"
# Static assets (no proxy)
ProxyPass "/static/" "!"
Alias "/static/" "/var/www/static/"
# Logging with client IP
LogFormat "%a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
ErrorLog /var/log/apache2/example_ssl_error.log
CustomLog /var/log/apache2/example_ssl_access.log combined
</VirtualHost>Troubleshooting and Verification
Verification Commands
# Test configuration
sudo apachectl configtest
# Check loaded modules
sudo apachectl -M | grep proxy
# Test backend connectivity
curl -I http://127.0.0.1:8080/health
# Monitor balancer status
curl http://localhost/balancer-manager
# Check headers are being forwarded
curl -H "X-Forwarded-For: 1.2.3.4" http://example.com/
# Debug logging (add to VirtualHost)
LogLevel proxy:trace5Common Issues and Solutions
| Issue | Symptom | Solution |
|---|---|---|
| Missing modules | 500 errors on proxy routes | Enable required proxy modules |
| Wrong client IP | Backend sees proxy IP | Configure mod_remoteip and trusted proxies |
| WebSocket failure | WS connections timeout | Use proxy_wstunnel and proper upgrade headers |
| SSL errors | Backend SSL verification fails | Set SSLProxyEngine and certificate verification |
| Timeout issues | Slow responses or timeouts | Adjust ProxyTimeout and connection settings |
Performance Tuning and Optimization
Optimize Apache for your specific workload and hardware resources. Regular performance testing is recommended after changes.
MPM Selection and Configuration
Use MPM event for modern setups with PHP-FPM, as it handles keepalives efficiently and scales better with HTTP/2:
Enable event MPM
# Debian/Ubuntu
sudo a2dismod mpm_prefork
sudo a2enmod mpm_event
sudo systemctl restart apache2
# RHEL-based: Ensure this line in /etc/httpd/conf.modules.d/00-mpm.conf
LoadModule mpm_event_module modules/mod_mpm_event.soMemory-Based MPM Tuning
Configure MPM settings based on your available memory and expected traffic:
| Server Size | StartServers | MinSpareThreads | MaxSpareThreads | ThreadLimit | ThreadsPerChild | MaxRequestWorkers | MaxConnectionsPerChild |
|---|---|---|---|---|---|---|---|
| Small (1GB RAM) | 2 | 25 | 75 | 64 | 25 | 75 | 10000 |
| Medium (4GB RAM) | 5 | 50 | 150 | 64 | 25 | 150 | 10000 |
| Large (8GB+ RAM) | 10 | 100 | 250 | 64 | 25 | 400 | 10000 |
<IfModule mpm_event_module>
StartServers 2
MinSpareThreads 25
MaxSpareThreads 75
ThreadsPerChild 25
MaxRequestWorkers 150
MaxConnectionsPerChild 0
</IfModule>KeepAlive Optimization
Proper KeepAlive settings reduce TCP overhead but consume resources:
KeepAlive On
KeepAliveTimeout 2
MaxKeepAliveRequests 100Note: Lower timeout values (2-5 seconds) work well for most sites. Increase if you have high-latency connections.
File Descriptor Limits
Increase file descriptor limits for high-traffic servers:
# Check current limits
ulimit -n
# System-wide limits in /etc/security/limits.conf
www-data soft nofile 65536
www-data hard nofile 65536
# Systemd service override
sudo systemctl edit apache2
[Service]
LimitNOFILE=65536Caching and compression
HTTP caching
Set Cache-Control and ETag from your app or via headers. mod_cache can
cache upstream responses. CacheQuickHandler on for faster path.
LoadModule cache_module modules/mod_cache.so
LoadModule cache_disk_module modules/mod_cache_disk.so
CacheQuickHandler on
CacheEnable disk "/"
CacheLock on
CacheDefaultExpire 300Compression
Gzip: mod_deflate. Brotli: mod_brotli for modern browsers.
# Gzip
AddOutputFilterByType DEFLATE text/html text/css application/javascript application/json image/svg+xml
# Brotli if available
AddOutputFilterByType BROTLI_COMPRESS text/html text/css application/javascript application/json image/svg+xmlStatic assets and ETags
# Far-future caching for versioned assets
<Location "/assets/">
ExpiresActive On
ExpiresDefault "access plus 1 year"
Header set Cache-Control "public, max-age=31536000, immutable"
</Location>
# Avoid weak validators on proxies
FileETag MTime SizeCompression best practices
# Reasonable defaults
DeflateCompressionLevel 6
AddOutputFilterByType DEFLATE text/html text/plain text/css application/javascript application/json image/svg+xml
# Skip already-compressed formats
SetEnvIfNoCase Request_URI "\.(?:jpg|jpeg|png|gif|webp|avif|zip|gz|bz2|7z)$" no-gzip
# Brotli if available
AddOutputFilterByType BROTLI_COMPRESS text/html text/plain text/css application/javascript application/json image/svg+xml
Verify
curl -I --compressed https://example.com/assets/app.css | grep -i 'content-encoding\|cache-control'Security Hardening and Best Practices
Implement these security measures to protect your web server from common threats. The following configurations follow security best practices while maintaining functionality for real-world applications.
Security Headers Configuration
Enable Required Modules
# Debian/Ubuntu
sudo a2enmod headers
sudo a2enmod rewrite
sudo systemctl reload apache2
# RHEL/Fedora - Ensure these are loaded in httpd.conf
# LoadModule headers_module modules/mod_headers.so
# LoadModule rewrite_module modules/mod_rewrite.soBasic Security Headers (Safe for Most Sites)
Start with these headers that won't break functionality:
# /etc/apache2/conf-available/security-headers.conf
# Enable HSTS only after confirming HTTPS works
# Start conservative
Header always set Strict-Transport-Security "max-age=86400"
# After verification across all subdomains, enable long max-age with subdomains
# Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
# Basic security headers
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
# Reduce server banner via core directives (set elsewhere):
# ServerTokens Prod
# ServerSignature Off
Header always unset X-Powered-ByAdvanced Security Headers (Test Thoroughly)
These headers provide enhanced security but require testing:
# Permissions Policy (formerly Feature Policy)
Header always set Permissions-Policy "geolocation=(), camera=(), microphone=(), payment=()"
# Cross-Origin policies
Header always set Cross-Origin-Opener-Policy "same-origin"
Header always set Cross-Origin-Embedder-Policy "require-corp"
# Note: These policies require cooperating origins. Third-party assets must send CORS or CORP or they will be blocked.
# Content Security Policy - Start with report-only mode
# Header always set Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' 'unsafe-inline' https:; style-src 'self' 'unsafe-inline' https:; img-src 'self' https: data:; font-src 'self' https:; connect-src 'self' https:; frame-ancestors 'self'; base-uri 'self'; form-action 'self'"CSP with Nonce Implementation
Advanced Feature: For dynamic nonce support, enable mod_unique_id:
# Debian/Ubuntu
sudo a2enmod unique_id
sudo systemctl reload apache2
# In VirtualHost or global config:
<IfModule mod_unique_id.c>
<IfModule mod_headers.c>
# Generate nonce for CSP
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-%{UNIQUE_ID}e'; style-src 'self' 'nonce-%{UNIQUE_ID}e'; img-src 'self' data: https:;"
</IfModule>
</IfModule>Warning: Nonce-based CSP requires server-side template processing and may break JavaScript functionality.
Directory and File Permissions
Implement least privilege access controls with practical permissions:
Secure File and Directory Permissions
# Document root - web user should have read access only
sudo chown -R root:www-data /var/www/html
sudo find /var/www/html -type f -exec chmod 640 {} \;
sudo find /var/www/html -type d -exec chmod 750 {} \;
# Writable directories (uploads, cache, sessions)
sudo chown -R www-data:www-data /var/www/html/uploads
sudo chown -R www-data:www-data /var/www/html/cache
sudo find /var/www/html/uploads -type f -exec chmod 640 {} \;
sudo find /var/www/html/uploads -type d -exec chmod 2750 {} \; # setgid bit
# Log directories
sudo chown -R root:adm /var/log/apache2
sudo chmod 755 /var/log/apache2
# Configuration files - root ownership, minimal permissions
sudo chown -R root:root /etc/apache2
sudo find /etc/apache2 -type f -exec chmod 644 {} \;
sudo find /etc/apache2 -type d -exec chmod 755 {} \;Distribution-Specific Web Users
| Distribution | User:Group | Notes |
|---|---|---|
| Debian/Ubuntu | www-data:www-data | Default Apache user |
| RHEL/Fedora/CentOS | apache:apache | Traditional user |
| openSUSE | wwwrun:www | SUSE-specific |
| Arch Linux | http:http | Minimal naming |
Advanced Security Configuration
Server Hardening Directives
# Hide server information
ServerTokens Prod
ServerSignature Off
# Disable TRACE
TraceEnable Off
# Limit request sizes to prevent DoS
LimitRequestBody 10485760 # 10MB
# Timeout settings for security
Timeout 60
KeepAliveTimeout 5
MaxKeepAliveRequests 100
# Disable server-side includes and CGI unless needed
Options -Includes -ExecCGI
# Disable .htaccess overrides for better performance and security
AllowOverride None
# Limit HTTP methods (Apache 2.4 syntax)
<LimitExcept GET POST HEAD>
Require all denied
</LimitExcept>Directory Protection
# Protect sensitive directories
<Directory "/var/www/html">
Options -Indexes -Includes -ExecCGI
AllowOverride None
Require all granted
# Block access to sensitive files
<FilesMatch "\.(env|ini|conf|config|log|sh|sql|tpl|twig|yml|yaml|bak|backup|swp)$">
Require all denied
</FilesMatch>
# Block access to hidden files (starting with .)
<FilesMatch "^\.">
Require all denied
</FilesMatch>
</Directory>
# Protect uploads directory
<Directory "/var/www/html/uploads">
Options -Indexes -Includes -ExecCGI
AllowOverride None
Require all granted
# Prevent PHP execution in uploads
<FilesMatch "\.php$">
Require all denied
</FilesMatch>
</Directory>ModSecurity Web Application Firewall
Installation and Basic Setup
# Debian/Ubuntu
sudo apt update
sudo apt install -y libapache2-mod-security2 modsecurity-crs
# RHEL/Fedora/CentOS
sudo dnf install -y mod_security mod_security_crs
# Enable ModSecurity
sudo a2enmod security2 # Debian/Ubuntu
# RHEL: Ensure LoadModule security2_module modules/mod_security2.so
# Create ModSecurity configuration directory
sudo mkdir -p /etc/modsecurityModSecurity Configuration
# Copy and configure main configuration
sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
# Edit configuration for production use
sudo nano /etc/modsecurity/modsecurity.conf
# Key configuration settings:
SecRuleEngine DetectionOnly # Start in detection-only mode
SecRequestBodyLimit 134217728 # 128MB
SecRequestBodyNoFilesLimit 131072 # 128KB
SecRequestBodyInMemoryLimit 131072
SecRequestBodyAccess On
SecRule REQUEST_HEADERS:Content-Type "text/xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML"
SecRule REQUEST_HEADERS:Content-Type "application/json" "id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON"OWASP Core Rule Set Setup
# Configure OWASP CRS (Debian/Ubuntu)
sudo cp -r /usr/share/modsecurity-crs /etc/apache2/
sudo mv /etc/apache2/modsecurity-crs /etc/apache2/modsecurity-crs-activated
# Enable CRS rules
sudo ln -s /etc/apache2/modsecurity-crs-activated/crs-setup.conf /etc/apache2/modsecurity-crs-setup.conf
sudo ln -s /etc/apache2/modsecurity-crs-activated/rules /etc/apache2/modsecurity-rules
# Include in Apache configuration
echo 'Include /etc/apache2/modsecurity-crs-setup.conf' | sudo tee -a /etc/apache2/mods-enabled/security2.conf
echo 'Include /etc/apache2/modsecurity-rules/*.conf' | sudo tee -a /etc/apache2/mods-enabled/security2.confTesting and Monitoring
# Test ModSecurity configuration
sudo apachectl -t
# Restart Apache
sudo systemctl restart apache2
# Test with a simple attack vector
curl -H "User-Agent: nikto" http://localhost/
# Check ModSecurity logs
sudo tail -f /var/log/apache2/modsec_audit.log
sudo tail -f /var/log/modsec_debug.log
# When confident, switch to blocking mode
# Change in /etc/modsecurity/modsecurity.conf:
# SecRuleEngine OnAdditional Security Measures
Fail2Ban Protection
# Install Fail2Ban
sudo apt install -y fail2ban # Debian/Ubuntu
sudo dnf install -y fail2ban # RHEL/Fedora
# Create Apache jail configuration
sudo tee /etc/fail2ban/jail.d/apache.conf >/dev/null <<'EOF'
[apache-auth]
enabled = true
port = http,https
filter = apache-auth
logpath = /var/log/apache2/*error.log
maxretry = 3
bantime = 3600
findtime = 600
[apache-badbots]
enabled = true
port = http,https
filter = apache-badbots
logpath = /var/log/apache2/*access.log
maxretry = 2
bantime = 86400
findtime = 600
[apache-botsearch]
enabled = true
port = http,https
filter = apache-botsearch
logpath = /var/log/apache2/*access.log
maxretry = 10
bantime = 86400
findtime = 600
EOF
# Start and enable Fail2Ban
sudo systemctl enable fail2ban
sudo systemctl start fail2banSSL/TLS Security Hardening
# Strong TLS configuration (in SSL VirtualHost)
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
TLSCipherSuite TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256
SSLHonorCipherOrder off
SSLSessionTickets off
# Enable OCSP stapling
SSLUseStapling on
SSLStaplingResponderTimeout 5
SSLStaplingReturnResponderErrors off
SSLStaplingCache "shmcb:logs/stapling-cache(150000)"Security Verification
Testing Commands
# Test security headers
curl -I https://yourdomain.com
# SSL/TLS testing
openssl s_client -connect yourdomain.com:443 -servername yourdomain.com
# Security scanning tools
sudo apt install -y nikto # Web vulnerability scanner
nikto -h https://yourdomain.com
# Check for open ports
sudo netstat -tulpn | grep :80
sudo netstat -tulpn | grep :443
# Verify file permissions
sudo find /var/www/html -type f -perm /o=w -ls # World-writable files
sudo find /var/www/html -type f -name "*.php" -perm /g=w -ls # Group-writable PHP filesFirewall and networking
Open 80 and 443.
UFW (Ubuntu)
sudo ufw allow "Apache Full"
sudo ufw statusfirewalld (RHEL/Fedora)
sudo firewall-cmd --add-service=http --add-service=https --permanent
sudo firewall-cmd --reload
sudo firewall-cmd --list-alliptables example
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPTAWS security groups
- Inbound: TCP 80 and 443 from 0.0.0.0/0 and ::/0 as needed.
- Outbound: allow all or as required.
nftables example (modern netfilter)
sudo nft add table inet filter
sudo nft add chain inet filter input { type filter hook input priority 0 \; policy drop \; }
sudo nft add rule inet filter input ct state established,related accept
sudo nft add rule inet filter input iif lo accept
sudo nft add rule inet filter input tcp dport {80,443} acceptSELinux custom TLS port (if using 8443):
sudo semanage port -a -t http_port_t -p tcp 8443Maintenance, Monitoring, and Troubleshooting
Establish routine procedures for monitoring, maintenance, and disaster recovery to ensure long-term server stability and security.
Real-Time Monitoring
Enable the status module for real-time server metrics:
# Enable status module
sudo a2enmod status # Debian/Ubuntu
# Or ensure LoadModule status_module modules/mod_status.so is enabled
# Configure status endpoint
<Location "/server-status">
SetHandler server-status
Require ip 192.168.1.0/24 # Restrict to internal networks
Require local # Allow localhost
</Location>
ExtendedStatus OnUse command-line tools for live diagnostics:
# Check current connections
sudo ss -tulpn | grep :80
# Monitor process resources
sudo ps aux | grep apache
sudo top -p $(pgrep -d',' -f apache)
# Check for resource limits
sudo cat /proc/$(pgrep apache2|head -1)/limits | grep "open files"Log Monitoring and Analysis
Check logs regularly for errors and suspicious activity.
# View access and error logs in real-time
sudo tail -F /var/log/apache2/*.log /var/log/httpd/*log
# View logs via systemd journal
sudo journalctl -u apache2|httpd -f -n 50Use GoAccess for a quick console-based analysis:
# Debian/Ubuntu
sudo apt install -y goaccess
sudo goaccess /var/log/apache2/access.log -o report.html --log-format=COMBINED
# RHEL/Fedora
sudo dnf install -y goaccess
sudo goaccess /var/log/httpd/access_log -o report.html --log-format=COMBINED
# openSUSE
sudo zypper install -y goaccess
sudo goaccess /var/log/apache2/access_log -o report.html --log-format=COMBINEDRegular Maintenance Tasks
Automate log rotation and certificate renewal checks.
Log Rotation
# Custom log rotation configuration
sudo tee /etc/logrotate.d/apache2-custom >/dev/null <<'EOF'
/var/log/apache2/*.log {
daily
missingok
rotate 52
compress
delaycompress
notifempty
create 644 root adm
sharedscripts
postrotate
systemctl reload apache2 > /dev/null 2>&1 || true
endscript
}
EOFCertificate Renewal Monitoring
Run this script via cron to get warnings 30 days before expiry:
#!/bin/bash
DOMAINS=("example.com" "www.example.com")
for domain in "${DOMAINS[@]}"; do
cert_path="/etc/letsencrypt/live/$domain/fullchain.pem"
if [[ ! -f "$cert_path" ]]; then
echo "WARNING: Certificate not found for $domain"
continue
fi
expiry_date=$(openssl x509 -in "$cert_path" -noout -enddate | cut -d= -f2)
expiry_epoch=$(date -d "$expiry_date" +%s 2>/dev/null || date -j -f "%b %d %T %Y %Z" "$expiry_date" +%s)
today_epoch=$(date +%s)
days_until_expiry=$(( (expiry_epoch - today_epoch) / 86400 ))
if [[ $days_until_expiry -lt 30 ]]; then
echo "WARNING: Certificate for $domain expires in $days_until_expiry days"
fi
doneBackup and Disaster Recovery
Regularly backup Apache configuration and critical files.
Configuration Backup Script
#!/bin/bash
BACKUP_DIR="/root/apache-backups"
DATE=$(date +%Y%m%d_%H%M%S)
CONFIG_DIR=""
mkdir -p "$BACKUP_DIR/$DATE"
if [[ -d "/etc/apache2" ]]; then CONFIG_DIR="/etc/apache2"; elif [[ -d "/etc/httpd" ]]; then CONFIG_DIR="/etc/httpd"; else echo "ERROR: No config dir" && exit 1; fi
cp -r "$CONFIG_DIR" "$BACKUP_DIR/$DATE/"
if [[ -d "/etc/letsencrypt" ]]; then cp -r /etc/letsencrypt "$BACKUP_DIR/$DATE/"; fi
tar -czf "$BACKUP_DIR/apache_backup_$DATE.tar.gz" -C "$BACKUP_DIR/$DATE" .
find "$BACKUP_DIR" -name "apache_backup_*.tar.gz" -mtime +30 -deleteEmergency Recovery
# Restore from last known good config
sudo systemctl stop apache2
sudo tar -xzf /root/apache-backups/apache_backup_latest.tar.gz -C /
sudo apachectl -t
sudo systemctl start apache2Common Issue Resolution
| Symptom | Possible Cause | Solution |
|---|---|---|
| Apache won't start | Syntax error in config | Run sudo apachectl -t to find the error. |
| Permission denied (logs) | Incorrect file/dir permissions | Check ls -l on /var/www and /var/log/apache2. |
| Permission denied (proxy) | SELinux policy | Run sudo setsebool -P httpd_can_network_connect on or check ausearch. |
| High memory usage | MPM misconfiguration | Lower MaxRequestWorkers in your MPM config. |
| SSL/TLS errors | Cert path/permission issue | Verify SSLCertificateFile paths and that privkey.pem is root-readable. |
| Port 80/443 in use | Another service (NGINX) | Run sudo ss -ltnp '( sport = :80 or sport = :443 )'. |
Port forwarding and NAT examples
Home router, OpenWrt style
Forward WAN:80 and WAN:443 to LAN host 192.168.1.10:80/443.
Linux DNAT with iptables:
# Forward external 8080 to internal 10.0.0.10:80
sudo sysctl -w net.ipv4.ip_forward=1
sudo iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination 10.0.0.10:80
sudo iptables -A FORWARD -p tcp -d 10.0.0.10 --dport 80 -j ACCEPTCloud load balancers
Use HTTP 80 redirect to HTTPS 443 at the LB if possible. Example, AWS ALB HTTP→HTTPS listener rule.
Health checks for cloud load balancers
Expose a simple endpoint for LBs:
<Location "/health">
Require ip 10.0.0.0/8 192.168.0.0/16 127.0.0.1/32
SetHandler server-status
</Location>Ensure security groups or firewall allow LB source IP ranges.
Questions
apache2 is the Debian/Ubuntu package name and service. httpd is the upstream name used by RHEL/Fedora/openSUSE/Arch/Amazon. Layout differs slightly as described above.
Event handles keepalives efficiently and scales better with HTTP/2. With PHP-FPM, you do not need mod_php, so prefork is unnecessary.
Add the header only after all subdomains serve HTTPS. Start with a shorter max-age, then increase to 31536000 and consider preload later. See OWASP secure headers.
Add Listen 8080 or Listen 8443 and create matching <VirtualHost *:PORT>. For TLS on custom ports with SELinux, label the port under http_port_t.
Experimental. Not widely packaged. Use a reverse proxy or CDN if you need HTTP/3 today.
proxy_fcgi (via SetHandler) is a specialized, highly efficient module to speak the FastCGI protocol directly to PHP-FPM. ProxyPass (using mod_proxy_http) is a general-purpose reverse proxy for speaking HTTP to any backend application server (like Node.js, Python, or Java). Using proxy_fcgi for PHP is more direct and performant.
This is almost always a permissions issue. Check three things:
1. File Permissions: Ensure your /var/www files are readable by the Apache user (e.g., www-data or apache).
2. SELinux (RHEL/Fedora): This is the most common cause. Check ausearch -m avc -ts recent for denials. You may need to set a boolean (like setsebool -P httpd_can_network_connect on for proxies) or relabel file contexts (restorecon -Rv /var/www/example).
3. AppArmor (Ubuntu): Less common, but check aa-status to see if the Apache profile is in complain or enforce mode and if it's blocking access to a non-standard path.
Conclusion
You have successfully installed and configured a secure, high-performance Apache web server on Linux. This guide covered the entire process, from initial installation on various distributions to advanced configuration.
By following these steps, you've set up name-based virtual hosts, secured your sites with Let's Encrypt certificates, and enabled modern protocols like HTTP/2. You've also implemented the recommended best practices for performance by using the MPM Event module and PHP-FPM, and hardened your server with crucial security headers, firewall rules, and file permissions.
With these robust monitoring, backup, and maintenance procedures in place, your web server is well-prepared for a production environment.
Resources
- Apache httpd 2.4 docs and module references. (opens in new window)
- RHEL Apache admin docs. (opens in new window)
- Ubuntu Server Guide for Apache. (opens in new window)
- openSUSE Apache guide. (opens in new window)
- ArchWiki Apache. (opens in new window)
- Certbot instructions and auto-renew details. (opens in new window)
- mod_cache and CacheQuickHandler. (opens in new window)
- mod_brotli and mod_deflate. (opens in new window)
- mod_proxy, mod_proxy_hcheck, mod_proxy_wstunnel. (opens in new window)
- mod_remoteip. (opens in new window)
- Apache mod_http2 documentation. (opens in new window)
- Mozilla SSL Configuration Generator. (opens in new window)
- CIS Apache HTTP Server Benchmark. (opens in new window)
- OWASP Secure Headers Project. (opens in new window)
- UFW usage. (opens in new window)
- OpenWrt port forwarding. (opens in new window)
- AWS security group examples. (opens in new window)
