Help us fix this page
If you found a broken link, missing page, or incorrect redirect, please let us know. Your report helps us improve the website for everyone.

The Apache HTTP Server is a fast, open-source web server that delivers static files and serves dynamic applications through PHP-FPM, Python WSGI, Node.js, or other backends via reverse proxy. It handles TLS termination, HTTP/2, URL rewriting, and caching. Administrators use it for single sites, multi-tenant hosting, and as an edge proxy in front of application servers.
This guide covers installation, service management, virtual host setup, HTTPS configuration, PHP-FPM integration, reverse proxy patterns, performance tuning, caching, security hardening, firewalls, monitoring, and troubleshooting on modern Linux distributions.
apache2 on Debian/Ubuntu, httpd on RHEL-based systems. Service names differ accordingly.proxy_fcgi. This combination provides the best performance and resource isolation.systemctl, apachectl -t, and curl.| Requirement | Details |
|---|---|
| Root or sudo access | Required for package installation and service management |
| 64-bit Linux | With package manager access and time sync enabled |
| DNS records | A/AAAA records pointing to your webserver for each hostname |
| Open TCP ports | 80 (HTTP) and 443 (HTTPS) at host and network firewalls |
| TLS 1.3 support | Requires Apache 2.4.43+ with OpenSSL 1.1.1 or newer |
| Minimum resources | 1 vCPU, 1-2 GB RAM, 10 GB free disk (plan extra for logs and caches) |
apachectl -v
openssl version
hostname -I
dig +short A example.com # or AAAA for IPv6| Distribution Family | Package | Service Name | Module Enabler |
|---|---|---|---|
| Debian, Ubuntu | apache2 | apache2 | a2enmod |
| RHEL, AlmaLinux, Rocky, CentOS Stream | httpd | httpd | Modules in conf.modules.d |
| Fedora | httpd | httpd | Modules in conf.modules.d |
| openSUSE Leap/Tumbleweed | apache2 | apache2 | a2enmod |
| Arch Linux | apache | httpd | Modules in conf.modules.d |
| Amazon Linux 2 / 2023 | httpd | httpd | Modules in conf.modules.d |
| Distribution | Document Root |
|---|---|
| Debian/Ubuntu/RHEL/Fedora/Arch/Amazon | /var/www/html |
| openSUSE | /srv/www/htdocs |
sudo apt update
sudo apt install -y apache2
sudo systemctl enable --now apache2Verify:
systemctl status apache2 --no-pager
apachectl -v
curl -I http://localhostsudo dnf install -y httpd
sudo systemctl enable --now httpdVerify:
systemctl status httpd --no-pager
httpd -v
curl -I http://localhostsudo dnf install -y httpd
sudo systemctl enable --now httpdsudo zypper install -y apache2
sudo systemctl enable --now apache2sudo pacman -Syu --noconfirm apache
sudo systemctl enable --now httpd# AL2
sudo yum install -y httpd
# AL2023
sudo dnf install -y httpd
sudo systemctl enable --now httpd| Operation | Debian/Ubuntu | RHEL/Fedora/Others |
|---|---|---|
| Start | sudo systemctl start apache2 | sudo systemctl start httpd |
| Stop | sudo systemctl stop apache2 | sudo systemctl stop httpd |
| Restart | sudo systemctl restart apache2 | sudo systemctl restart httpd |
| Reload | sudo systemctl reload apache2 | sudo systemctl reload httpd |
| Enable | sudo systemctl enable apache2 | sudo systemctl enable httpd |
| Status | sudo systemctl status apache2 | sudo systemctl status httpd |
| Graceful | sudo apachectl -k graceful | sudo apachectl -k graceful |
| 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 |
# Always test before reloading
sudo apachectl -t
# Alternative syntax
sudo apachectl configtest
# Show parsed configuration
sudo apachectl -t -D DUMP_RUN_CFG
# List loaded modules
sudo apachectl -M
# Show virtual host configuration
sudo apachectl -S/etc/apache2/
├── apache2.conf # Main configuration
├── ports.conf # Port bindings
├── sites-available/ # Virtual host definitions
├── sites-enabled/ # Enabled virtual hosts (symlinks)
├── mods-available/ # Available modules
├── mods-enabled/ # Enabled modules (symlinks)
└── envvars # Environment variablesLogs: /var/log/apache2/
/etc/httpd/
├── conf/
│ └── httpd.conf # Main configuration
├── conf.d/ # Additional configuration
└── conf.modules.d/ # Module loadingLogs: /var/log/httpd/
/etc/apache2/
├── httpd.conf # Main configuration
├── conf.d/ # Additional configuration
└── vhosts.d/ # Virtual host definitionsLogs: /var/log/apache2/
Modern Apache does not require NameVirtualHost. Use VirtualHost with *:80 and *:443.
Debian/Ubuntu (/etc/apache2/sites-available/example-http.conf):RHEL/Fedora (/etc/httpd/conf.d/example-http.conf):
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example
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 apache2Listen 192.0.2.10:80
<VirtualHost 192.0.2.10:80>
ServerName app.example.com
DocumentRoot /var/www/app
ErrorLog ${APACHE_LOG_DIR}/app_error.log
CustomLog ${APACHE_LOG_DIR}/app_access.log combined
</VirtualHost><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>
# SSL Configuration
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
# OCSP Stapling
SSLUseStapling on
SSLStaplingCache shmcb:/var/run/ocsp(128000)
# HTTP/2
Protocols h2 http/1.1
ErrorLog ${APACHE_LOG_DIR}/example_ssl_error.log
CustomLog ${APACHE_LOG_DIR}/example_ssl_access.log combined
</VirtualHost>Ubuntu (Snap – recommended):
sudo snap install core && sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbotRHEL/Fedora/openSUSE/Arch:
# Install from distribution repository
sudo dnf install -y certbot python3-certbot-apache # RHEL/Fedora
sudo zypper install -y certbot # openSUSE
sudo pacman -S certbot # ArchApache 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
sudo systemctl reload apache2 # or httpdopenssl s_client -connect example.com:443 -servername example.com -status | grep -E "Protocol|Cipher|issuer|Verify return code|OCSP"The Snap-based certbot installs a systemd timer that runs twice daily.
# Test renewal
sudo certbot renew --dry-run
# Check timer
systemctl list-timers | grep -i certbotEnable mod_http2 and add Protocols h2 http/1.1 to TLS virtual hosts.
| Distribution | Enable Command |
|---|---|
| Debian/Ubuntu | sudo a2enmod http2 && sudo systemctl reload apache2 |
| RHEL/Fedora | Ensure LoadModule http2_module modules/mod_http2.so in conf.modules.d/ |
| openSUSE | sudo a2enmod http2 && sudo systemctl reload apache2 |
Verify ALPN:
curl -I --http2 https://example.com
openssl s_client -alpn h2 -connect example.com:443 -servername example.com | grep -i alpnStatus: Apache httpd has no officially shipped HTTP/3 module as of version 2.4.65. Use a reverse proxy with HTTP/3 support-Nginx 1.25+, Caddy, or a CDN.
Using PHP-FPM with Apache’s MPM event provides better performance, resource isolation, and scalability compared to mod_php.
sudo apt update
sudo apt install -y php8.3-fpm
# Enable modules
sudo a2enmod proxy proxy_fcgi setenvif
sudo a2dismod mpm_prefork
sudo a2enmod mpm_event
# Create PHP-FPM configuration
sudo tee /etc/apache2/conf-available/php8.3-fpm.conf >/dev/null <<'EOF'
<FilesMatch "\.php$">
SetHandler "proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost/"
</FilesMatch>
DirectoryIndex index.php index.html
<Files "\.(env|ini|log|sh|sql|tpl|twig|yml)$">
Require all denied
</Files>
EOF
sudo a2enconf php8.3-fpm
sudo systemctl enable php8.3-fpm
sudo systemctl start php8.3-fpm
sudo apachectl -t && sudo systemctl restart apache2sudo dnf install -y php-fpm
sudo tee /etc/httpd/conf.d/php-fpm.conf >/dev/null <<'EOF'
<FilesMatch "\.php$">
SetHandler "proxy:unix:/run/php-fpm/www.sock|fcgi://localhost/"
</FilesMatch>
DirectoryIndex index.php index.html
<Files "\.(env|ini|log|sh|sql|tpl|twig|yml)$">
Require all denied
</Files>
EOF
sudo systemctl enable php-fpm
sudo systemctl start php-fpm
sudo apachectl -t && sudo systemctl restart httpdsudo zypper install -y php8-fpm
sudo tee /etc/apache2/conf.d/php-fpm.conf >/dev/null <<'EOF'
<FilesMatch "\.php$">
SetHandler "proxy:unix:/run/php8-fpm.sock|fcgi://localhost/"
</FilesMatch>
DirectoryIndex index.php index.html
EOF
sudo a2enmod proxy proxy_fcgi
sudo systemctl enable php8-fpm
sudo systemctl start php8-fpm
sudo apachectl -t && sudo systemctl restart apache2sudo pacman -Syu --noconfirm php php-fpm
sudo tee /etc/httpd/conf/extra/php-fpm.conf >/dev/null <<'EOF'
<FilesMatch "\.php$">
SetHandler "proxy:unix:/run/php-fpm/php-fpm.sock|fcgi://localhost/"
</FilesMatch>
DirectoryIndex index.php index.html
EOF
echo "Include conf/extra/php-fpm.conf" | sudo tee -a /etc/httpd/conf/httpd.conf
sudo systemctl enable php-fpm
sudo systemctl start php-fpm
sudo apachectl -t && sudo systemctl restart httpdEdit the pool configuration:
| Distribution | Location |
|---|---|
| Debian/Ubuntu | /etc/php/8.3/fpm/pool.d/www.conf |
| RHEL/Fedora | /etc/php-fpm.d/www.conf |
| openSUSE | /etc/php8/fpm/pool.d/www.conf |
| Arch Linux | /etc/php/php-fpm.d/www.conf |
Optimized settings:
pm = dynamic
pm.max_children = 30
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 8
pm.max_requests = 500
listen = /run/php/php8.3-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
request_terminate_timeout = 60s
request_slowlog_timeout = 30sDebian/Ubuntu:
sudo a2enmod proxy proxy_http proxy_balancer lbmethod_byrequests headers
sudo a2enmod proxy_hcheck proxy_wstunnel slotmem_shm remoteip
sudo apachectl -t && sudo systemctl reload apache2RHEL/Fedora: Ensure these modules are loaded in conf.modules.d/00-proxy.conf.
ProxyPass "/" "http://127.0.0.1:8080/"
ProxyPassReverse "/" "http://127.0.0.1:8080/"
ProxyPreserveHost On
ProxyTimeout 60
ProxyRequests Off<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
BalancerMember "http://10.0.0.13:8080" hcheck=on hcinterval=30 status=+H
ProxySet lbmethod=byrequests
</Proxy>
ProxyPass "/" "balancer://appcluster/"
ProxyPassReverse "/" "balancer://appcluster/"LoadModule remoteip_module modules/mod_remoteip.so
RemoteIPHeader X-Forwarded-For
RemoteIPInternalProxy 10.0.0.0/8
RemoteIPInternalProxy 172.16.0.0/12
RemoteIPInternalProxy 192.168.0.0/16ProxyPass "/ws/" "ws://127.0.0.1:9000/"
ProxyPassReverse "/ws/" "ws://127.0.0.1:9000/"
# Alternative with rewrite
RewriteEngine On
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule "^/ws/(.*)" "ws://127.0.0.1:9000/$1" [P,L]# Enable headers module
# Debian/Ubuntu: sudo a2enmod headers
# RHEL/Fedora: LoadModule headers_module modules/mod_headers.so
# Basic 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"
# HSTS - start conservative, then increase
Header always set Strict-Transport-Security "max-age=86400"
# After verification: max-age=31536000; includeSubDomains
# Permissions-Policy (experimental)
Header always set Permissions-Policy "geolocation=(), camera=(), microphone=(), payment=()"
# Server information
ServerTokens Prod
ServerSignature Off
Header always unset X-Powered-By<Directory "/var/www/html">
Options -Indexes -Includes -ExecCGI
AllowOverride None
Require all granted
<FilesMatch "\.(env|ini|conf|config|log|sh|sql|tpl|twig|yml|yaml|bak|backup|swp)$">
Require all denied
</FilesMatch>
<FilesMatch "^\.">
Require all denied
</FilesMatch>
</Directory>
# Protect uploads directory from PHP execution
<Directory "/var/www/html/uploads">
<FilesMatch "\.php$">
Require all denied
</FilesMatch>
</Directory><LimitExcept GET POST HEAD>
Require all denied
</LimitExcept>
TraceEnable OffSSLProtocol 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
SSLUseStapling on
SSLStaplingResponderTimeout 5
SSLStaplingCache "shmcb:logs/stapling-cache(150000)"Debian/Ubuntu:
sudo apt install -y libapache2-mod-security2 modsecurity-crs
sudo a2enmod security2RHEL/Fedora:
sudo dnf install -y mod_security mod_security_crsBasic configuration:
sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.confSet in /etc/modsecurity/modsecurity.conf:
SecRuleEngine DetectionOnly # Start in detection-only mode
SecRequestBodyLimit 134217728 # 128MBSELinux (RHEL/Fedora):
sudo setsebool -P httpd_can_network_connect on
sudo setsebool -P httpd_execmem on
sudo setsebool -P httpd_unified on
sudo ausearch -m avc -ts recent # Check for denialsAppArmor (Ubuntu/Debian):
sudo aa-status
sudo aa-complain apache2 # Temporarily complain mode for testing
sudo aa-enforce apache2 # Re-enforcesudo apt install -y fail2ban # Debian/Ubuntu
sudo dnf install -y fail2ban # RHEL/Fedora
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
EOF
sudo systemctl enable fail2ban
sudo systemctl start fail2bansudo ufw allow "Apache Full"
sudo ufw statussudo firewall-cmd --add-service=http --add-service=https --permanent
sudo firewall-cmd --reload
sudo firewall-cmd --list-allsudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPTsudo 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} acceptUse MPM event for modern setups with PHP-FPM:
Debian/Ubuntu:
sudo a2dismod mpm_prefork
sudo a2enmod mpm_event
sudo systemctl restart apache2RHEL/Fedora: Ensure LoadModule mpm_event_module modules/mod_mpm_event.so in 00-mpm.conf.
| Server Size | StartServers | MinSpareThreads | MaxSpareThreads | ThreadsPerChild | MaxRequestWorkers |
|---|---|---|---|---|---|
| Small (1GB) | 2 | 25 | 75 | 25 | 75 |
| Medium (4GB) | 5 | 50 | 150 | 25 | 150 |
| Large (8GB+) | 10 | 100 | 250 | 25 | 400 |
<IfModule mpm_event_module>
StartServers 2
MinSpareThreads 25
MaxSpareThreads 75
ThreadsPerChild 25
MaxRequestWorkers 150
MaxConnectionsPerChild 0
</IfModule>KeepAlive On
KeepAliveTimeout 2
MaxKeepAliveRequests 100# Check current limits
ulimit -n
# System-wide limits (Debian/Ubuntu)
# /etc/security/limits.conf
www-data soft nofile 65536
www-data hard nofile 65536
# Systemd service override
sudo systemctl edit apache2
[Service]
LimitNOFILE=65536# Gzip
AddOutputFilterByType DEFLATE text/html text/css application/javascript application/json image/svg+xml
DeflateCompressionLevel 6
# 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/css application/javascript application/json image/svg+xmlLoadModule cache_module modules/mod_cache.so
LoadModule cache_disk_module modules/mod_cache_disk.so
CacheQuickHandler on
CacheEnable disk "/"
CacheLock on
CacheDefaultExpire 300
# 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># Enable status module
# Debian/Ubuntu: sudo a2enmod status
<Location "/server-status">
SetHandler server-status
Require ip 192.168.1.0/24
Require local
</Location>
ExtendedStatus OnLive diagnostics:
sudo ss -tulpn | grep :80
sudo ps aux | grep apache
sudo top -p $(pgrep -d',' -f apache)# Real-time log monitoring
sudo tail -F /var/log/apache2/*.log /var/log/httpd/*log
# Systemd journal
sudo journalctl -u apache2 -fsudo apt install -y goaccess # Debian/Ubuntu
sudo dnf install -y goaccess # RHEL/Fedora
sudo goaccess /var/log/apache2/access.log -o report.html --log-format=COMBINEDsudo 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
}
EOFBackup 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:
sudo systemctl stop apache2
sudo tar -xzf /root/apache-backups/apache_backup_latest.tar.gz -C /
sudo apachectl -t
sudo systemctl start apache2# Test security headers
curl -I https://example.com
# SSL/TLS testing
openssl s_client -connect example.com:443 -servername example.com
# Test with OWASP dependency-check or nikto
nikto -h https://example.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| Symptom | Possible Cause | Solution |
|---|---|---|
| Apache won’t start | Syntax error in config | sudo apachectl -t to find the error |
| Permission denied (logs) | Incorrect permissions | Check ls -l on /var/www and /var/log/apache2 |
| Permission denied (proxy) | SELinux policy | sudo setsebool -P httpd_can_network_connect on |
| High memory usage | MPM misconfiguration | Lower MaxRequestWorkers in MPM config |
| SSL/TLS errors | Cert path/permission issue | Verify SSLCertificateFile paths; privkey.pem must be root-readable |
| Port 80/443 in use | Another service running | sudo ss -ltnp '( sport = :80 or sport = :443 )' |
| 502 Bad Gateway (PHP-FPM) | Socket permissions | Check socket file exists; verify www-data/apache user has access |
| SELinux blocking | AVC denials | ausearch -m avc -ts recent; sealert -a /var/log/audit/audit.log |
apache2 on Debian/Ubuntu/openSUSE, httpd on RHEL/Fedora/Arch/Amazon.mod_php with MPM prefork.certbot renew --dry-run.sudo apachectl -t prevents service disruption.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 virtual host configuration, HTTPS with Let’s Encrypt, HTTP/2, PHP-FPM integration, reverse proxy setup, performance tuning, caching, security hardening, and monitoring.
By following these steps, you’ve implemented the recommended best practices for performance using MPM event and PHP-FPM. You’ve hardened your server with security headers, firewall rules, and file permissions. You’ve set up robust monitoring and backup procedures.
Apache remains one of the most flexible and widely deployed web servers in the world. With this foundation, your web server is well-prepared for a production environment. Continue to monitor logs, keep software updated, and revisit security configurations as your application evolves.
Need help with your web infrastructure? Playful Sparkle has been engineering digital products since 2004, offering Web Development, App Development, and Infrastructure & DevOps services. Contact us to discuss how we can help you build and maintain a secure, high-performance web presence.