Comprehensive Guide for Setting Up Apache Web Server on Linux

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 apache2 on Debian and Ubuntu, httpd elsewhere.
  • 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, and curl.

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 IPv6

Supported Linux distributions and package names

  • Debian and Ubuntu: package apache2, service apache2. Modules via a2enmod.
  • RHEL, AlmaLinux, Rocky, CentOS Stream, Fedora, Amazon Linux, openSUSE, Arch: package httpd, service httpd. Modules in conf.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-brotli or mod_brotli depending on your Linux distribution.
  • Certbot: snapd + certbot snap (Ubuntu), or certbot from your Linux distribution repository.
  • PHP-FPM: php-fpm (RHEL/Fedora/openSUSE/Arch), or version-specific packages like php8.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 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

Verify

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

openSUSE Leap/Tumbleweed

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

Verify

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

Arch Linux

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

Verify

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

Amazon Linux 2 / 2023

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

Verify

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

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

Reload 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.
CommandActive ConnectionsDowntimeUse Case
systemctl reloadPreservedNoneConfig file changes
apachectl gracefulCompleted gracefullyNoneProduction config updates
systemctl restartDroppedBriefModule 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 :443

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

Directory layout and key config files

  • Debian/Ubuntu: /etc/apache2/ with sites-available, sites-enabled, mods-available, mods-enabled, ports.conf, and envvars. 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/html except openSUSE /srv/www/htdocs.

Common paths by Linux distribution

Linux distributionConfig rootVhost locationsDocumentRootLogs
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 -M

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

Global ServerName and IP-based vhost

# Avoid 'Could not reliably determine the server's fully qualified domain name' warning
  ServerName example.com

IP-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/certbot

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

# Reload Apache after first issuance
sudo systemctl reload apache2|httpd

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

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

The 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.so in conf.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 alpn

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

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

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

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

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

AppArmor (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.apache2

PHP-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 = -10

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

Common Issues and Solutions

IssueSymptomSolution
Socket permissions502 Bad GatewayCheck socket file exists and www-data/apache user has access
SELinux blockingPermission denied in logsUse setsebool and check audit logs
Wrong MPMApache won't startUse MPM event, not prefork with PHP-FPM
PHP memory limitsWhite screen or errorsIncrease memory_limit in php.ini
Missing modulesApache configtest failsEnable 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.log

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

RHEL/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 httpd

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

Path-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\"" combined

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

WebSocket 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-By
# 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:trace5

Common Issues and Solutions

IssueSymptomSolution
Missing modules500 errors on proxy routesEnable required proxy modules
Wrong client IPBackend sees proxy IPConfigure mod_remoteip and trusted proxies
WebSocket failureWS connections timeoutUse proxy_wstunnel and proper upgrade headers
SSL errorsBackend SSL verification failsSet SSLProxyEngine and certificate verification
Timeout issuesSlow responses or timeoutsAdjust 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.so

Memory-Based MPM Tuning

Configure MPM settings based on your available memory and expected traffic:

Server SizeStartServersMinSpareThreadsMaxSpareThreadsThreadLimitThreadsPerChildMaxRequestWorkersMaxConnectionsPerChild
Small (1GB RAM)2257564257510000
Medium (4GB RAM)550150642515010000
Large (8GB+ RAM)10100250642540010000
<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 100

Note: 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=65536

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

Compression

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

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

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

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

Advanced 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

DistributionUser:GroupNotes
Debian/Ubuntuwww-data:www-dataDefault Apache user
RHEL/Fedora/CentOSapache:apacheTraditional user
openSUSEwwwrun:wwwSUSE-specific
Arch Linuxhttp:httpMinimal 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/modsecurity

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

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

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

SSL/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 files

Firewall and networking

Open 80 and 443.

UFW (Ubuntu)

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 example

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

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

SELinux custom TLS port (if using 8443):

sudo semanage port -a -t http_port_t -p tcp 8443

Maintenance, 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 On

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

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

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

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

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

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

Common Issue Resolution

SymptomPossible CauseSolution
Apache won't startSyntax error in configRun sudo apachectl -t to find the error.
Permission denied (logs)Incorrect file/dir permissionsCheck ls -l on /var/www and /var/log/apache2.
Permission denied (proxy)SELinux policyRun sudo setsebool -P httpd_can_network_connect on or check ausearch.
High memory usageMPM misconfigurationLower MaxRequestWorkers in your MPM config.
SSL/TLS errorsCert path/permission issueVerify SSLCertificateFile paths and that privkey.pem is root-readable.
Port 80/443 in useAnother 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 ACCEPT

Cloud 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

Zsolt Oroszlány

Article author Zsolt Oroszlány

CEO of the creative agency Playful Sparkle, brings over 20 years of expertise in graphic design and programming. He leads innovative projects and spends his free time working out, watching movies, and experimenting with new CSS features. Zsolt's dedication to his work and hobbies drives his success in the creative industry.

Let’s amplify your success together!

Request a Free Quote