Comprehensive Guide for Setting Up Apache Web Server on Linux

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.

TL;DR

  • Install apache2 on Debian/Ubuntu, httpd on RHEL-based systems. Service names differ accordingly.
  • Use MPM event with PHP-FPM via proxy_fcgi. This combination provides the best performance and resource isolation.
  • HTTP/2 is enabled by default on most modern distributions. HTTP/3 remains experimental for Apache as of version 2.4.65-use a reverse proxy like Nginx 1.25+, Caddy, or a CDN for QUIC/HTTP/3.
  • Use Let’s Encrypt with certbot. Auto-renewal runs via systemd timer or cron.
  • Configure TLS for TLS 1.2 and 1.3 only. Add security headers.
  • Open only ports 80 and 443 in your firewall.
  • Verify every step with systemctl, apachectl -t, and curl.

Prerequisites

RequirementDetails
Root or sudo accessRequired for package installation and service management
64-bit LinuxWith package manager access and time sync enabled
DNS recordsA/AAAA records pointing to your webserver for each hostname
Open TCP ports80 (HTTP) and 443 (HTTPS) at host and network firewalls
TLS 1.3 supportRequires Apache 2.4.43+ with OpenSSL 1.1.1 or newer
Minimum resources1 vCPU, 1-2 GB RAM, 10 GB free disk (plan extra for logs and caches)

Quick Pre-Installation Checks

apachectl -v
openssl version
hostname -I
dig +short A example.com   # or AAAA for IPv6

Supported Linux Distributions and Package Names

Distribution FamilyPackageService NameModule Enabler
Debian, Ubuntuapache2apache2a2enmod
RHEL, AlmaLinux, Rocky, CentOS StreamhttpdhttpdModules in conf.modules.d
FedorahttpdhttpdModules in conf.modules.d
openSUSE Leap/Tumbleweedapache2apache2a2enmod
Arch LinuxapachehttpdModules in conf.modules.d
Amazon Linux 2 / 2023httpdhttpdModules in conf.modules.d

Document Root Locations

DistributionDocument Root
Debian/Ubuntu/RHEL/Fedora/Arch/Amazon/var/www/html
openSUSE/srv/www/htdocs

Installation by Distribution

Debian / Ubuntu (LTS)

sudo apt update
sudo apt install -y apache2
sudo systemctl enable --now apache2

Verify:

systemctl status apache2 --no-pager
apachectl -v
curl -I http://localhost

RHEL 8/9, AlmaLinux, Rocky, CentOS Stream

sudo dnf install -y httpd
sudo systemctl enable --now httpd

Verify:

systemctl status httpd --no-pager
httpd -v
curl -I http://localhost

Fedora

sudo dnf install -y httpd
sudo systemctl enable --now httpd

openSUSE Leap/Tumbleweed

sudo zypper install -y apache2
sudo systemctl enable --now apache2

Arch Linux

sudo pacman -Syu --noconfirm apache
sudo systemctl enable --now httpd

Amazon Linux 2 / 2023

# AL2
sudo yum install -y httpd
# AL2023
sudo dnf install -y httpd
sudo systemctl enable --now httpd

Basic Service Management

Common Commands

OperationDebian/UbuntuRHEL/Fedora/Others
Startsudo systemctl start apache2sudo systemctl start httpd
Stopsudo systemctl stop apache2sudo systemctl stop httpd
Restartsudo systemctl restart apache2sudo systemctl restart httpd
Reloadsudo systemctl reload apache2sudo systemctl reload httpd
Enablesudo systemctl enable apache2sudo systemctl enable httpd
Statussudo systemctl status apache2sudo systemctl status httpd
Gracefulsudo apachectl -k gracefulsudo apachectl -k graceful

Reload vs. Restart vs. Graceful

CommandActive ConnectionsDowntimeUse Case
systemctl reloadPreservedNoneConfig file changes
apachectl gracefulCompleted gracefullyNoneProduction config updates
systemctl restartDroppedBriefModule changes, major updates

Configuration Testing

# 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

Directory Layout and Key Configuration Files

Debian/Ubuntu

/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 variables

Logs: /var/log/apache2/

RHEL/Fedora/Alma/Rocky/Arch/Amazon

/etc/httpd/
├── conf/
│   └── httpd.conf        # Main configuration
├── conf.d/               # Additional configuration
└── conf.modules.d/       # Module loading

Logs: /var/log/httpd/

openSUSE

/etc/apache2/
├── httpd.conf            # Main configuration
├── conf.d/               # Additional configuration
└── vhosts.d/             # Virtual host definitions

Logs: /var/log/apache2/

Virtual Host Configuration

Modern Apache does not require NameVirtualHost. Use VirtualHost with *:80 and *:443.

HTTP Virtual Host with Redirect to HTTPS

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 apache2

IP-Based Virtual Host

Listen 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>

HTTPS Virtual Host

<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>

HTTPS with Let’s Encrypt

Install Certbot

Ubuntu (Snap – recommended):

sudo snap install core && sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot

RHEL/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                               # Arch

Obtain Certificate

Apache plugin (where supported):

sudo certbot --apache -d example.com -d www.example.com

Webroot method (works everywhere):

sudo certbot certonly --webroot -w /var/www/example -d example.com -d www.example.com
sudo systemctl reload apache2   # or httpd

Verify Certificate

openssl s_client -connect example.com:443 -servername example.com -status | grep -E "Protocol|Cipher|issuer|Verify return code|OCSP"

Auto-Renewal

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 certbot

HTTP/2 and HTTP/3

HTTP/2

Enable mod_http2 and add Protocols h2 http/1.1 to TLS virtual hosts.

DistributionEnable Command
Debian/Ubuntusudo a2enmod http2 && sudo systemctl reload apache2
RHEL/FedoraEnsure LoadModule http2_module modules/mod_http2.so in conf.modules.d/
openSUSEsudo 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 alpn

HTTP/3

Status: 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.

PHP Integration with PHP-FPM via proxy_fcgi

Using PHP-FPM with Apache’s MPM event provides better performance, resource isolation, and scalability compared to mod_php.

Debian/Ubuntu

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 apache2

RHEL/Fedora/Alma/Rocky

sudo 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 httpd

openSUSE

sudo 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 apache2

Arch Linux

sudo 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 httpd

PHP-FPM Pool Tuning

Edit the pool configuration:

DistributionLocation
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 = 30s

Reverse Proxy Configuration

Enable Proxy Modules

Debian/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 apache2

RHEL/Fedora: Ensure these modules are loaded in conf.modules.d/00-proxy.conf.

Basic Reverse Proxy

ProxyPass "/" "http://127.0.0.1:8080/"
ProxyPassReverse "/" "http://127.0.0.1:8080/"
ProxyPreserveHost On
ProxyTimeout 60
ProxyRequests Off

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
    BalancerMember "http://10.0.0.13:8080" hcheck=on hcinterval=30 status=+H
    ProxySet lbmethod=byrequests
</Proxy>

ProxyPass "/" "balancer://appcluster/"
ProxyPassReverse "/" "balancer://appcluster/"

Client IP Forwarding

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/16

WebSocket Proxy

ProxyPass "/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]

Security Hardening

Security Headers

# 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 Protection

<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>

Disable Unnecessary Methods

<LimitExcept GET POST HEAD>
    Require all denied
</LimitExcept>

TraceEnable Off

SSL/TLS Hardening

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

SSLUseStapling on
SSLStaplingResponderTimeout 5
SSLStaplingCache "shmcb:logs/stapling-cache(150000)"

ModSecurity (Web Application Firewall)

Debian/Ubuntu:

sudo apt install -y libapache2-mod-security2 modsecurity-crs
sudo a2enmod security2

RHEL/Fedora:

sudo dnf install -y mod_security mod_security_crs

Basic configuration:

sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf

Set in /etc/modsecurity/modsecurity.conf:

SecRuleEngine DetectionOnly   # Start in detection-only mode
SecRequestBodyLimit 134217728 # 128MB

SELinux/AppArmor

SELinux (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 denials

AppArmor (Ubuntu/Debian):

sudo aa-status
sudo aa-complain apache2   # Temporarily complain mode for testing
sudo aa-enforce apache2    # Re-enforce

Fail2Ban Protection

sudo 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 fail2ban

Firewall Configuration

UFW (Ubuntu/Debian)

sudo ufw allow "Apache Full"
sudo ufw status

firewalld (RHEL/Fedora)

sudo firewall-cmd --add-service=http --add-service=https --permanent
sudo firewall-cmd --reload
sudo firewall-cmd --list-all

iptables

sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT

nftables

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} accept

Performance Tuning

MPM Selection and Configuration

Use MPM event for modern setups with PHP-FPM:

Debian/Ubuntu:

sudo a2dismod mpm_prefork
sudo a2enmod mpm_event
sudo systemctl restart apache2

RHEL/Fedora: Ensure LoadModule mpm_event_module modules/mod_mpm_event.so in 00-mpm.conf.

Memory-Based MPM Settings

Server SizeStartServersMinSpareThreadsMaxSpareThreadsThreadsPerChildMaxRequestWorkers
Small (1GB)225752575
Medium (4GB)55015025150
Large (8GB+)1010025025400
<IfModule mpm_event_module>
    StartServers 2
    MinSpareThreads 25
    MaxSpareThreads 75
    ThreadsPerChild 25
    MaxRequestWorkers 150
    MaxConnectionsPerChild 0
</IfModule>

KeepAlive Optimization

KeepAlive On
KeepAliveTimeout 2
MaxKeepAliveRequests 100

File Descriptor Limits

# 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

Compression

# 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+xml

Caching

LoadModule 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>

Monitoring and Maintenance

Real-Time Monitoring

# 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 On

Live diagnostics:

sudo ss -tulpn | grep :80
sudo ps aux | grep apache
sudo top -p $(pgrep -d',' -f apache)

Log Monitoring

# Real-time log monitoring
sudo tail -F /var/log/apache2/*.log /var/log/httpd/*log

# Systemd journal
sudo journalctl -u apache2 -f

GoAccess (Console Log Analysis)

sudo 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=COMBINED

Log Rotation

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
}
EOF

Backup and Disaster Recovery

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 -delete

Emergency recovery:

sudo systemctl stop apache2
sudo tar -xzf /root/apache-backups/apache_backup_latest.tar.gz -C /
sudo apachectl -t
sudo systemctl start apache2

Security Verification

# 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

Troubleshooting Reference

SymptomPossible CauseSolution
Apache won’t startSyntax error in configsudo apachectl -t to find the error
Permission denied (logs)Incorrect permissionsCheck ls -l on /var/www and /var/log/apache2
Permission denied (proxy)SELinux policysudo setsebool -P httpd_can_network_connect on
High memory usageMPM misconfigurationLower MaxRequestWorkers in MPM config
SSL/TLS errorsCert path/permission issueVerify SSLCertificateFile paths; privkey.pem must be root-readable
Port 80/443 in useAnother service runningsudo ss -ltnp '( sport = :80 or sport = :443 )'
502 Bad Gateway (PHP-FPM)Socket permissionsCheck socket file exists; verify www-data/apache user has access
SELinux blockingAVC denialsausearch -m avc -ts recent; sealert -a /var/log/audit/audit.log

Key Takeaways

  1. Installation varies by distribution. Use apache2 on Debian/Ubuntu/openSUSE, httpd on RHEL/Fedora/Arch/Amazon.
  2. Use MPM event with PHP-FPM. This combination provides better performance and resource isolation than mod_php with MPM prefork.
  3. HTTP/2 is available; HTTP/3 is not. As of Apache 2.4.65, HTTP/3 is experimental. Use Nginx, Caddy, or a CDN for QUIC.
  4. Use Let’s Encrypt with certbot. Auto-renewal runs via systemd timer. Verify renewal with certbot renew --dry-run.
  5. Test every configuration change. sudo apachectl -t prevents service disruption.
  6. Security is layered. Combine security headers, directory protection, SELinux/AppArmor, and ModSecurity for defense in depth.
  7. Monitor regularly. Use status module, logs, and GoAccess. Back up configuration files. Document recovery procedures.

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 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.

Resources

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.

Was this helpful - Post
Zsolt Oroszlány

Zsolt Oroszlány

Founder & Chief Creative Officer of Playful Sparkle since 2004, combining business leadership, digital strategy, design, and software engineering to help organizations build effective digital solutions. Regularly publishes insights on web development, SEO, design, and emerging technologies.