Apache HTTP Server remains the most widely deployed general-purpose web server on Linux. It is stable, well-documented, and flexible enough to serve static sites, dynamic apps, and reverse-proxied backends. You will install a secure baseline with system service management, a firewall, virtual hosts, TLS, and a few tuning passes so you can go from “new droplet” to “production-ready.” You will need a Linux VM with shell access; if you use a cloud VPS, make sure your DigitalOcean account is set up before you begin, as we will assume you can create a droplet and SSH into it. We start with packages, then layer on configuration and security so each step builds on the last.
Prerequisites
Use a fresh Linux server with a non-root sudo user and an A record pointed at your server’s public IP. Update your package index and base system first to avoid dependency surprises, then confirm that ports 80 and 443 are open in your provider’s control panel. With that in place, you can install the web server and verify it runs before touching configuration.
Update the system
Ubuntu or Debian:
sudo apt update
sudo apt -y upgrade
RHEL, AlmaLinux, Rocky, or Fedora:
sudo dnf -y upgrade
Install Apache
Package names differ slightly by family. After installation, enable the service so it survives reboots and start it now to verify the default page loads.
Ubuntu or Debian:
sudo apt -y install apache2
sudo systemctl enable --now apache2
RHEL, AlmaLinux, or Rocky:
sudo dnf -y install httpd
sudo systemctl enable --now httpd
Fedora:
sudo dnf -y install httpd
sudo systemctl enable --now httpd
Open a browser to http://<server-ip>
to confirm the default Apache test page. If it fails to load, check the firewall next.
Configure the firewall
A running service is only useful if the network allows it. Allow HTTP now so you can validate configuration, then add HTTPS before enabling TLS. This prevents unnecessary downtime while you work through certificates.
Ubuntu or Debian with UFW:
sudo ufw allow "Apache"
sudo ufw status
RHEL family with firewalld:
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --reload
sudo firewall-cmd --list-all
Understand the filesystem layout
Knowing where Apache looks for configuration and content reduces guesswork when something does not work. Debian-based systems use site-level includes; RHEL-based systems centralize under a single config tree. The implication is simple: follow the platform conventions to avoid conflicts with package updates.
- Ubuntu/Debian:
Config root/etc/apache2/
, main file/etc/apache2/apache2.conf
, modules in/etc/apache2/mods-*
, sites in/etc/apache2/sites-available/
with symlinks insites-enabled/
, web root/var/www/html
. - RHEL/AlmaLinux/Rocky/Fedora:
Config root/etc/httpd/
, main file/etc/httpd/conf/httpd.conf
, additional files under/etc/httpd/conf.d/
, web root/var/www/html
.
We will create a dedicated virtual host and document root rather than editing the default site, which keeps your changes isolated and easier to roll back.
Create a site and document root
Create a directory for your domain, assign ownership to your deploy user, and place a minimal index file. This validates that your virtual host points to the expected path before you add TLS or rewrites.
sudo mkdir -p /var/www/example.com/public
sudo chown -R $USER:$USER /var/www/example.com
printf '%s\n' '<!doctype html><title>It works</title><h1>Apache is serving example.com</h1>' > /var/www/example.com/public/index.html
Define a virtual host
A virtual host (vhost) maps a requested hostname to a configuration block. Start with a simple HTTP vhost and log files in a dedicated directory. This structure keeps logs per site, which simplifies troubleshooting and analytics later.
Ubuntu or Debian:
sudo tee /etc/apache2/sites-available/example.com.conf >/dev/null <<'CONF'
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com/public
<Directory /var/www/example.com/public>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/example.com_error.log
CustomLog ${APACHE_LOG_DIR}/example.com_access.log combined
</VirtualHost>
CONF
sudo a2ensite example.com.conf
sudo a2dissite 000-default.conf
sudo systemctl reload apache2
RHEL, AlmaLinux, Rocky, or Fedora:
sudo tee /etc/httpd/conf.d/example.com.conf >/dev/null <<'CONF'
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com/public
<Directory /var/www/example.com/public>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog /var/log/httpd/example.com_error.log
CustomLog /var/log/httpd/example.com_access.log combined
</VirtualHost>
CONF
sudo systemctl reload httpd
Browse to http://example.com
after updating your DNS A record. If the default page still appears, the vhost is mis-selected; ensure ServerName
matches the Host header and that only one site answers on port 80.
Enable essential modules
Apache loads features as modules. Enabling only what you need reduces memory footprint and attack surface. You will use mod_ssl
for TLS, mod_rewrite
for clean URLs, and mod_headers
for security headers. We will add mod_http2
for modern browsers on TLS.
Ubuntu or Debian:
sudo a2enmod ssl rewrite headers http2
sudo systemctl reload apache2
RHEL family:
# ssl and rewrite ship by default; ensure they are loaded
sudo sed -i 's/^#LoadModule ssl_module/LoadModule ssl_module/' /etc/httpd/conf.modules.d/00-ssl.conf
sudo sed -i 's/^#LoadModule rewrite_module/LoadModule rewrite_module/' /etc/httpd/conf.modules.d/00-base.conf
echo "LoadModule http2_module modules/mod_http2.so" | sudo tee /etc/httpd/conf.modules.d/10-http2.conf
sudo systemctl restart httpd
Obtain and install TLS certificates
Use Let’s Encrypt via Certbot to automate certificate issuance and renewal. Installing the web server plugin lets Certbot edit your vhost and add a secure redirect. Enabling HTTPS early ensures that downstream configuration, like security headers and HTTP/2, applies immediately.
Ubuntu or Debian:
sudo apt -y install certbot python3-certbot-apache
sudo certbot --apache -d example.com -d www.example.com --redirect --hsts
RHEL, AlmaLinux, or Rocky:
sudo dnf -y install certbot python3-certbot-apache
sudo certbot --apache -d example.com -d www.example.com --redirect --hsts
Open the firewall for HTTPS if you have not already.
UFW:
sudo ufw allow "Apache Full"
firewalld:
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
Confirm automatic renewals:
sudo systemctl status certbot.timer
sudo certbot renew --dry-run
Add baseline security headers
Security headers reduce common risks such as clickjacking and MIME sniffing. Apply them globally so all vhosts inherit safe defaults, then override per site if an app needs relaxed policies. This approach centralizes your security posture while allowing exceptions.
Ubuntu or Debian:
sudo tee /etc/apache2/conf-available/security-headers.conf >/dev/null <<'CONF'
<IfModule mod_headers.c>
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set X-XSS-Protection "0"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
</IfModule>
CONF
sudo a2enconf security-headers
sudo systemctl reload apache2
RHEL family:
sudo tee /etc/httpd/conf.d/security-headers.conf >/dev/null <<'CONF'
<IfModule mod_headers.c>
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set X-XSS-Protection "0"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
</IfModule>
CONF
sudo systemctl reload httpd
Optimize Apache for your instance
Apache uses Multi-Processing Modules (MPMs) to handle connections. event
is best for TLS and HTTP/2 because it scales keep-alives efficiently. Right-size worker counts to your vCPU and memory so you avoid swapping under load.
Ubuntu or Debian:
sudo a2dismod mpm_prefork
sudo a2enmod mpm_event
sudo systemctl restart apache2
RHEL family:
# Ensure event MPM is active
sudo sed -i 's/^LoadModule mpm_prefork_module/#LoadModule mpm_prefork_module/' /etc/httpd/conf.modules.d/00-mpm.conf
sudo sed -i 's/^#LoadModule mpm_event_module/LoadModule mpm_event_module/' /etc/httpd/conf.modules.d/00-mpm.conf
sudo systemctl restart httpd
Tune worker settings. Start conservatively, then load test and iterate.
Ubuntu or Debian:
sudo tee /etc/apache2/conf-available/mpm_event_tuning.conf >/dev/null <<'CONF'
<IfModule mpm_event_module>
ServerLimit 16
StartServers 2
ThreadLimit 64
ThreadsPerChild 32
MaxRequestWorkers 512
MaxConnectionsPerChild 0
</IfModule>
CONF
sudo a2enconf mpm_event_tuning
sudo systemctl reload apache2
RHEL family:
sudo tee /etc/httpd/conf.d/mpm_event_tuning.conf >/dev/null <<'CONF'
<IfModule mpm_event_module>
ServerLimit 16
StartServers 2
ThreadLimit 64
ThreadsPerChild 32
MaxRequestWorkers 512
MaxConnectionsPerChild 0
</IfModule>
CONF
sudo systemctl reload httpd
These values suit a 2–4 vCPU VM with moderate traffic. If MaxRequestWorkers
is too high for memory, processes will swap; if too low, Apache queues requests. Plan to run a quick load test and revisit these numbers.
Serve PHP or an application backend
Many apps need PHP or a reverse proxy to another service. You have two common paths. Use php-fpm
with proxy_fcgi
for PHP stacks, or mod_proxy
to send requests to an upstream such as Node, Python, or Go. Keeping application runtimes out of Apache worker processes improves stability.
PHP via php-fpm (Ubuntu or Debian):
sudo apt -y install php-fpm libapache2-mod-fcgid
sudo a2enmod proxy_fcgi setenvif
sudo a2enconf php*-fpm
sudo systemctl reload apache2
PHP via php-fpm (RHEL family):
sudo dnf -y install php-fpm
sudo systemctl enable --now php-fpm
echo "LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so" | sudo tee /etc/httpd/conf.modules.d/10-proxy_fcgi.conf
sudo systemctl reload httpd
Reverse proxy to an upstream app:
# Enable proxy modules if needed
sudo bash -lc 'apachectl -M | grep proxy || true'
# Ubuntu/Debian:
sudo a2enmod proxy proxy_http
sudo systemctl reload apache2
# RHEL family:
echo "LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so" | sudo tee /etc/httpd/conf.modules.d/10-proxy_http.conf
sudo systemctl reload httpd
Add a location block in your vhost to forward traffic:
ProxyPass /app http://127.0.0.1:3000/
ProxyPassReverse /app http://127.0.0.1:3000/
Handle SELinux and permissions (RHEL family)
If SELinux is enforcing, Apache may not read your content or connect to backends until you set the correct contexts and booleans. Correct labeling avoids brittle workarounds and keeps the system policy intact.
# Allow Apache to connect to network backends
sudo setsebool -P httpd_can_network_connect 1
# Label your custom web root
sudo semanage fcontext -a -t httpd_sys_content_t "/var/www/example.com(/.*)?"
sudo restorecon -Rv /var/www/example.com
Log management and rotation
Logs support both debugging and monitoring. Keep them per site, rotate them predictably, and ship them if you have a central log stack. Use the combined format for analytics and keep error logs clean to spot regressions quickly.
Confirm rotation configuration:
Ubuntu or Debian:
sudo cat /etc/logrotate.d/apache2
RHEL family:
sudo cat /etc/logrotate.d/httpd
Tail logs while testing:
sudo tail -f /var/log/apache2/*log # Ubuntu/Debian
# or
sudo tail -f /var/log/httpd/*log # RHEL family
Hardening checklist
You reduce information leakage and shrink your attack surface by removing modules and tokens you do not need. Hide version banners, disable directory indexes, and prefer least privilege for file ownership. These changes are low risk and provide immediate benefits.
Ubuntu or Debian:
# Hide version tokens
sudo sed -i 's/^#*ServerTokens.*/ServerTokens Prod/' /etc/apache2/conf-available/security.conf
sudo sed -i 's/^#*ServerSignature.*/ServerSignature Off/' /etc/apache2/conf-available/security.conf
sudo a2enconf security
# Disable autoindex if not needed
sudo a2dismod autoindex
sudo systemctl reload apache2
RHEL family:
sudo tee -a /etc/httpd/conf.d/security.conf >/dev/null <<'CONF'
ServerTokens Prod
ServerSignature Off
CONF
# Disable autoindex if loaded
sudo sed -i 's/^LoadModule autoindex_module/#LoadModule autoindex_module/' /etc/httpd/conf.modules.d/00-base.conf
sudo systemctl reload httpd
Set sensible file permissions:
sudo chown -R $USER:$USER /var/www/example.com
find /var/www/example.com -type d -exec chmod 755 {} \;
find /var/www/example.com -type f -exec chmod 644 {} \;
Graceful reloads and zero-downtime deploys
Reloading keeps connections alive while applying changes. Use configtest
before a reload to catch syntax errors, then rely on systemd to manage the process. This avoids accidental service interruptions during routine updates.
sudo apachectl configtest
sudo systemctl reload apache2 # Ubuntu/Debian
# or
sudo systemctl reload httpd # RHEL family
For content deploys, update files atomically and avoid partial writes:
rsync -az --delete site_build/ /var/www/example.com/public/
Basic troubleshooting
When something breaks, change one thing at a time and check logs. Most issues trace to DNS, firewall rules, vhost selection, or file permissions. Validate each layer from the network inward so you avoid chasing symptoms.
# Is Apache listening on 80/443?
sudo ss -tlnp | egrep ':80|:443'
# Which vhost will answer a given Host header?
sudo apachectl -S
# Quick syntax check
sudo apachectl configtest
# Live log view while reproducing the error
sudo tail -f /var/log/apache2/error.log /var/log/apache2/access.log # Ubuntu/Debian
# or
sudo tail -f /var/log/httpd/error_log /var/log/httpd/access_log # RHEL family
If HTTPS fails, verify that the certificate files exist and that renewal timers are active. If rewrites misbehave, temporarily disable .htaccess
overrides in the vhost and move rules into the main config for clarity.
What you have now
You installed Apache, opened the firewall, created a dedicated vhost and document root, enabled HTTP/2 with TLS, added security headers, tuned the event MPM, and prepared PHP or reverse proxying for application backends. You also set log rotation, hardened version disclosure, and learned a reproducible way to test and reload changes. From here, add a CI-driven deploy, integrate metrics with mod_status
behind access control, and run a short load test to resize MaxRequestWorkers
and thread counts.
Quick status recap
# Service and enabled state
systemctl status apache2 || systemctl status httpd
# Active vhosts
apachectl -S
# Certificate expiry
sudo certbot certificates
With this baseline, you can safely host a static site, a PHP application, or a proxied service on your Linux server. Next, script these steps for repeatable environments and consider an image snapshot in your provider so scaling out remains straightforward.