How to Install Apache on Ubuntu 24.04 — Web Server Setup
Quick Summary
This guide walks you through installing and configuring the Apache HTTP Server (apache2) on Ubuntu 24.04 LTS. By the end, you will have a hardened, production-ready web server with virtual hosts, HTTPS via Let's Encrypt, HTTP/2, caching, compression, and a tuned MPM event worker — all behind a properly configured UFW firewall.
Already comfortable with Apache? Jump to Install Apache via apt or Create Your First Virtual Host.
Table of Contents
- What is Apache?
- Apache vs Nginx — Which Should You Use?
- Prerequisites
- Install Apache via apt
- Verify the Installation
- Configure the UFW Firewall
- Apache Directory Structure
- Enable Essential Modules
- Create Your First Virtual Host
- Secure Your Site with Let's Encrypt SSL
- Enable HTTP/2
- Using .htaccess for URL Rewrites
- Switch to the Event MPM for Better Performance
- Install ModSecurity (Optional WAF)
- Reverse Proxy to a Backend App
- Compression with mod_deflate
- Caching Static Assets
- Security Hardening
- Log Rotation
- Troubleshooting
- FAQ
- Next Steps
What is Apache?
Apache HTTP Server, commonly called just "Apache" or "httpd", is the oldest widely used open-source web server on the internet. It was first released in 1995 by the Apache Software Foundation, and for more than a decade it powered the majority of public websites. Even in 2026, Apache still runs a substantial share of the web — especially WordPress sites, legacy LAMP stacks, and shared hosting platforms.
At its core, Apache is a process-based HTTP server that listens on ports 80 and 443, parses incoming HTTP requests, and returns responses. What made Apache successful is its modular architecture: almost every piece of functionality — SSL, URL rewriting, compression, authentication, caching, proxying — is implemented as a loadable module. You enable only what you need and leave the rest disabled.
Apache's other defining feature is the .htaccess file. Any directory served by Apache can contain a .htaccess file with directives that override the server configuration for that directory. This is why Apache dominates shared hosting: a customer can configure redirects, authentication, and caching without root access to the server. No other mainstream web server supports per-directory configuration quite like Apache.
On Ubuntu the package is called apache2, and the Debian/Ubuntu maintainers provide a particularly clean directory layout (sites-available, sites-enabled, mods-available, mods-enabled) with helper commands like a2ensite, a2enmod, and a2enconf that make managing complex configurations straightforward.
Apache vs Nginx — Which Should You Use?
Before you install Apache, make sure it is the right tool. The two dominant web servers on Linux are Apache and Nginx, and each has genuine strengths.
Apache is the right choice when:
- You run PHP applications (WordPress, Joomla, Drupal, Magento, Moodle). The
mod_phpand PHP-FPM integrations on Apache are mature and well documented. - You need
.htaccesssupport. Applications like WordPress ship with.htaccessfiles that assume Apache. Porting them to Nginx requires translating every rule into server config. - You host multiple tenants who need per-directory configuration without root.
- You use complex authentication schemes — LDAP, Kerberos, client certificates. Apache's
mod_auth_*ecosystem is unmatched. - You have a legacy stack and Apache is what your team already knows.
- You serve static files, images, or large downloads at high concurrency. Nginx's event-driven model uses less memory per connection.
- You need a reverse proxy or load balancer in front of Node.js, Python, Go, or Rails applications.
- You terminate SSL for tens of thousands of concurrent connections.
- You value a single config file over per-directory overrides.
.htaccess rules. With modern Apache (MPM event + HTTP/2 + mod_http2), the performance gap has narrowed significantly. For most single-VPS workloads, either server will perform well. Pick the one whose configuration model matches your applications.Prerequisites
Before starting, make sure you have:
- A VPS running Ubuntu 24.04 LTS with at least 1 GB RAM (2 GB recommended for production)
- Root or a user with
sudoprivileges - A domain name with an A record pointing to your VPS (required for SSL)
- Ports 80 (HTTP) and 443 (HTTPS) reachable from the internet
sudo apt update && sudo apt upgrade -yInstall Apache via apt
Ubuntu's official apache2 package is always a good choice — it is backported, security-patched, and integrates with systemd, logrotate, and the Debian module helpers.
sudo apt install apache2 -yThis installs the Apache 2.4.x binary, the default MPM (prefork on most fresh installs), and a minimal set of enabled modules. The installer also starts the service and enables it on boot.
Confirm the installed version:
apache2 -vYou should see output similar to:
Server version: Apache/2.4.58 (Ubuntu)
Server built: 2026-01-15T10:23:17Verify the Installation
Check that the service is running:
sudo systemctl status apache2You should see active (running) in green. If it is not running, start it:
sudo systemctl start apache2
sudo systemctl enable apache2Now test that Apache is serving pages. Open a browser and navigate to your VPS IP:
http://YOUR_SERVER_IP/You should see the default "Apache2 Ubuntu Default Page" — a purple banner with links to documentation and configuration hints. If you see this page, Apache is installed correctly.
From the command line you can also run:
curl -I http://localhost/You should get back HTTP/1.1 200 OK with a Server: Apache/2.4.58 (Ubuntu) header.
Configure the UFW Firewall
Ubuntu 24.04 ships with UFW (Uncomplicated Firewall). If you have not configured it yet, open Apache's ports now.
Check available application profiles:
sudo ufw app listYou will see three Apache profiles:
- Apache — HTTP only (port 80)
- Apache Secure — HTTPS only (port 443)
- Apache Full — both HTTP and HTTPS
sudo ufw allow OpenSSH
sudo ufw allow 'Apache Full'
sudo ufw enable
sudo ufw statusOutput should show:
Status: active
To Action From -- ------ ---- OpenSSH ALLOW Anywhere Apache Full ALLOW Anywhere
Apache Directory Structure
Ubuntu's Apache layout is organized for clarity. Spend a minute understanding it before you edit anything.
/etc/apache2/
├── apache2.conf # Main configuration file (loads everything else)
├── ports.conf # Listen directives (which ports Apache binds)
├── envvars # Environment variables for the apache2 process
├── magic # MIME type magic database
├── conf-available/ # Available snippets (security.conf, charset.conf, ...)
├── conf-enabled/ # Symlinks to enabled snippets
├── mods-available/ # Available modules (ssl.load, rewrite.load, ...)
├── mods-enabled/ # Symlinks to enabled modules
├── sites-available/ # Available virtual hosts (000-default.conf, ...)
└── sites-enabled/ # Symlinks to enabled virtual hostsKey points:
- Never edit files in
mods-enabled/orsites-enabled/directly. They are symlinks. Edit the originals inmods-available/orsites-available/. - Use the
a2*helpers to toggle things on and off:
a2enmod / a2dismod for modules
- a2ensite / a2dissite for virtual hosts
- a2enconf / a2disconf for config snippets
- Web content defaults to
/var/www/html/, but you can place sites anywhere. Many admins use/var/www/example.com/public/per domain. - Logs live in
/var/log/apache2/—access.loganderror.logby default, with per-site logs configurable in each virtual host.
Enable Essential Modules
A fresh install enables a minimal module set. For a modern production web server, turn on the following:
sudo a2enmod ssl
sudo a2enmod rewrite
sudo a2enmod headers
sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod http2
sudo a2enmod deflate
sudo a2enmod expires
sudo systemctl restart apache2What each module does:
ssl— terminates HTTPS. Required for Let's Encrypt.rewrite— pattern-based URL rewriting. Required by WordPress, Laravel, and most modern frameworks.headers— lets you set, append, and remove HTTP headers (HSTS, CSP, cache-control, etc).proxy+proxy_http— reverse proxy to backend apps over HTTP.http2— HTTP/2 support for multiplexed connections and header compression.deflate— gzip compression of responses.expires— setExpiresheaders for browser caching of static assets.
apache2ctl -MCreate Your First Virtual Host
Virtual hosts let a single Apache instance serve multiple domains. Each domain has its own configuration file in sites-available/.
Create a document root:
sudo mkdir -p /var/www/example.com/public
sudo chown -R $USER:$USER /var/www/example.com
echo '<h1>example.com works</h1>' | sudo tee /var/www/example.com/public/index.htmlCreate the virtual host config:
sudo nano /etc/apache2/sites-available/example.com.confPaste the following (replace example.com with your domain):
<VirtualHost *:80> ServerName example.com ServerAlias www.example.com ServerAdmin [email protected] 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>
A few notes on this config:
Options -Indexesdisables automatic directory listings when noindex.htmlis present.AllowOverride Alllets.htaccessfiles override server config within this directory. If you do not use.htaccess, set this toNonefor a small performance improvement.Require all grantedis the Apache 2.4 access-control directive replacing the oldAllow from all.
sudo a2ensite example.com.conf
sudo a2dissite 000-default.conf
sudo apache2ctl configtest
sudo systemctl reload apache2apache2ctl configtest catches syntax errors before you reload. Always run it after editing a config file.
Visit http://example.com/ in a browser and you should see your "example.com works" heading.
Secure Your Site with Let's Encrypt SSL
HTTPS is mandatory in 2026. Certbot automates the entire certificate issuance and renewal process for Let's Encrypt.
Install Certbot with the Apache plugin:
sudo apt install certbot python3-certbot-apache -yRequest and install a certificate:
sudo certbot --apache -d example.com -d www.example.comCertbot will:
/etc/apache2/sites-available/example.com-le-ssl.conf with the HTTPS virtual host.Certbot also installs a systemd timer that renews certificates automatically. Confirm it is scheduled:
sudo systemctl list-timers | grep certbotYou can dry-run the renewal at any time:
sudo certbot renew --dry-runVisit https://example.com/ — you should see your site served with a valid SSL certificate.
Enable HTTP/2
HTTP/2 dramatically speeds up page loads by multiplexing many requests over a single TCP connection and compressing headers. You already enabled mod_http2 above. Now activate the protocol in your SSL virtual host.
Edit the SSL config Certbot created:
sudo nano /etc/apache2/sites-available/example.com-le-ssl.confAdd the Protocols directive inside <VirtualHost *:443>:
<VirtualHost *:443> ServerName example.com ServerAlias www.example.com DocumentRoot /var/www/example.com/publicProtocols h2 http/1.1
SSLEngine on SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
# Strong SSL settings 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 SSLHonorCipherOrder off SSLSessionTickets off
# HSTS (only after you have confirmed HTTPS works) Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"
<Directory /var/www/example.com/public> Options -Indexes +FollowSymLinks AllowOverride All Require all granted </Directory>
ErrorLog ${APACHE_LOG_DIR}/example.com-ssl-error.log CustomLog ${APACHE_LOG_DIR}/example.com-ssl-access.log combined </VirtualHost>
Reload Apache:
sudo apache2ctl configtest && sudo systemctl reload apache2Confirm HTTP/2 with curl:
curl -I --http2 https://example.com/You should see HTTP/2 200.
Note: HTTP/2 requires the event MPM (see next section) or worker MPM. The default prefork MPM does not support HTTP/2.
Using .htaccess for URL Rewrites
Because the virtual host above uses AllowOverride All, you can drop a .htaccess file in your document root to configure redirects, rewrites, and headers without editing server config.
Example /var/www/example.com/public/.htaccess:
# Force HTTPS (redundant if Certbot already redirects, but harmless)
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]Force www (or strip www — pick one)
RewriteCond %{HTTP_HOST} ^example\.com$ [NC]
RewriteRule ^(.*)$ https://www.example.com/$1 [L,R=301]Remove trailing slashes from URLs
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+)/$ /$1 [L,R=301]Pretty URLs for a PHP app (WordPress-style)
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]Block access to hidden files (.git, .env, etc)
RewriteRule "(^|/)\." - [F]Custom error pages
ErrorDocument 404 /404.html
ErrorDocument 500 /500.html.htaccess takes effect immediately — no Apache reload required. If a rule does not work, check /var/log/apache2/error.log for parse errors.
Switch to the Event MPM for Better Performance
Apache's Multi-Processing Module (MPM) determines how it handles concurrent requests. Ubuntu often installs the prefork MPM by default, which spawns one process per connection — simple but memory-hungry and HTTP/2-incompatible.
For modern workloads, switch to the event MPM, which uses a thread-per-connection model with a dedicated listener thread and scales to thousands of concurrent connections.
sudo a2dismod mpm_prefork
sudo a2enmod mpm_event
sudo systemctl restart apache2Caution: If you use>mod_php(the in-process PHP module), it requiresmpm_prefork. Withmpm_eventyou must run PHP via PHP-FPM instead:
bash> sudo apt install php php-fpm libapache2-mod-fcgid -y
> sudo a2enmod proxy_fcgi setenvif
> sudo a2enconf php8.3-fpm
> sudo systemctl restart apache2
>Tune the event MPM in /etc/apache2/mods-available/mpm_event.conf:
<IfModule mpm_event_module>
StartServers 4
MinSpareThreads 25
MaxSpareThreads 75
ThreadLimit 64
ThreadsPerChild 25
MaxRequestWorkers 400
MaxConnectionsPerChild 10000
</IfModule>Rough sizing guide: MaxRequestWorkers should be roughly (available RAM in MB) / (average worker memory in MB). On a 2 GB VPS running a lightweight PHP app, 150 is a reasonable starting point.
Install ModSecurity (Optional WAF)
ModSecurity is a web application firewall that inspects incoming requests and blocks known attack patterns (SQL injection, XSS, path traversal). It is optional but highly recommended for public-facing sites.
sudo apt install libapache2-mod-security2 -y
sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
sudo sed -i 's/SecRuleEngine DetectionOnly/SecRuleEngine On/' /etc/modsecurity/modsecurity.confInstall the OWASP Core Rule Set:
sudo apt install modsecurity-crs -y
sudo systemctl restart apache2Test that ModSecurity is blocking attacks:
curl "http://example.com/?id=1' OR '1'='1"You should get a 403 Forbidden. Check /var/log/apache2/modsec_audit.log for the triggered rule.
Tune rules as needed — ModSecurity is aggressive by default and often triggers false positives on legitimate admin traffic.
Reverse Proxy to a Backend App
Apache makes an excellent reverse proxy in front of Node.js, Python, Go, or Java backends. Create a virtual host that forwards requests to a backend running on localhost:3000:
<VirtualHost *:443> ServerName app.example.com DocumentRoot /var/www/app.example.com/publicProtocols h2 http/1.1
SSLEngine on SSLCertificateFile /etc/letsencrypt/live/app.example.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/app.example.com/privkey.pem
# Preserve host + client IP ProxyPreserveHost On ProxyRequests Off RequestHeader set X-Forwarded-Proto "https" RequestHeader set X-Forwarded-Port "443"
# Proxy everything except /static (served directly) ProxyPass /static ! ProxyPass / http://127.0.0.1:3000/ ProxyPassReverse / http://127.0.0.1:3000/
ErrorLog ${APACHE_LOG_DIR}/app-error.log CustomLog ${APACHE_LOG_DIR}/app-access.log combined </VirtualHost>
For WebSocket support (Socket.io, live updates), also enable mod_proxy_wstunnel:
sudo a2enmod proxy_wstunnelThen add:
RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /(.*) ws://127.0.0.1:3000/$1 [P,L]Compression with mod_deflate
You enabled mod_deflate earlier. Now configure what gets compressed. Edit /etc/apache2/mods-available/deflate.conf:
<IfModule mod_deflate.c>
<IfModule mod_filter.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript
AddOutputFilterByType DEFLATE application/javascript application/json application/xml
AddOutputFilterByType DEFLATE application/xhtml+xml application/rss+xml
AddOutputFilterByType DEFLATE image/svg+xml font/ttf font/otf font/woff font/woff2
</IfModule>
</IfModule>Do not compress already-compressed formats (JPEG, PNG, WebP, MP4, ZIP) — you waste CPU for no gain.
Test:
curl -H "Accept-Encoding: gzip" -I https://example.com/You should see Content-Encoding: gzip in the response.
Caching Static Assets
Tell browsers to cache CSS, JS, and images aggressively. Add to your virtual host or .htaccess:
<IfModule mod_expires.c> ExpiresActive OnExpiresByType text/css "access plus 1 year" ExpiresByType application/javascript "access plus 1 year" ExpiresByType image/jpeg "access plus 1 year" ExpiresByType image/png "access plus 1 year" ExpiresByType image/webp "access plus 1 year" ExpiresByType image/svg+xml "access plus 1 year" ExpiresByType font/woff2 "access plus 1 year" ExpiresByType text/html "access plus 10 minutes" </IfModule>
<IfModule mod_headers.c> <FilesMatch "\.(css|js|jpg|jpeg|png|webp|svg|woff2|ttf|ico)$"> Header set Cache-Control "public, max-age=31536000, immutable" </FilesMatch> </IfModule>
Use hashed filenames in production (app.a1b2c3.css) so you can cache for a year and bust the cache by changing the filename on deploy.
Security Hardening
Apache's default config reveals more than it should. Lock it down.
Edit /etc/apache2/conf-available/security.conf:
# Hide Apache version in Server header and error pages
ServerTokens Prod
ServerSignature OffDisable TRACE method (prevents cross-site tracing)
TraceEnable OffPrevent clickjacking
Header always set X-Frame-Options "SAMEORIGIN"Prevent MIME-type sniffing
Header always set X-Content-Type-Options "nosniff"Basic referrer policy
Header always set Referrer-Policy "strict-origin-when-cross-origin"Disable directory listing globally
<Directory /var/www/>
Options -Indexes
</Directory>Reload:
sudo systemctl reload apache2Additional hardening checklist:
- Run Apache as the
www-datauser (default on Ubuntu — do not change). - Keep
apache2updated viaunattended-upgrades. - Disable
AllowOverride Allwhere you do not need.htaccess. - Set strict file permissions:
sudo chown -R www-data:www-data /var/www/example.com && sudo find /var/www/example.com -type d -exec chmod 755 {} \; && sudo find /var/www/example.com -type f -exec chmod 644 {} \; - Install
fail2banto block brute-force attempts against your login endpoints.
Log Rotation
Ubuntu ships with logrotate pre-configured for Apache. The rules live in /etc/logrotate.d/apache2:
/var/log/apache2/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 640 root adm
sharedscripts
postrotate
if invoke-rc.d apache2 status > /dev/null 2>&1; then
invoke-rc.d apache2 reload > /dev/null 2>&1
fi
endscript
}This rotates logs daily, keeps 14 days, and gzips old files. For very high-traffic sites, change daily to hourly or rotate to a higher number. Test the config without applying:
sudo logrotate -d /etc/logrotate.d/apache2Troubleshooting
"403 Forbidden"
Usually a file permission problem or a missing Require all granted. Check:
index.html or index.php?Options -Indexes preventing listing when no index file exists?www-data have read access: sudo -u www-data cat /var/www/example.com/public/index.html?<Directory> block include Require all granted?"500 Internal Server Error"
Check /var/log/apache2/error.log and the per-site error log. Common causes:
- Syntax error in
.htaccess - Missing PHP module (if running PHP)
- Wrong file permissions on scripts
- Too-restrictive
Optionsthat disable required features
"Address already in use: AH00072: make_sock: could not bind to address [::]:80"
Another process is using port 80. Find it:
sudo ss -tlnp | grep :80Usually this is Nginx or an old Apache instance. Stop it:
sudo systemctl stop nginx
or
sudo pkill -9 httpd
sudo systemctl start apache2Certbot fails with "Timeout during connect"
Cloudflare or your firewall is blocking the HTTP-01 challenge. Either:
- Temporarily pause Cloudflare proxying (grey cloud) and retry.
- Use the DNS-01 challenge:
sudo certbot --dns-cloudflare -d example.com.
Apache serves the default page instead of your site
Your virtual host is not being picked up. Check:
sudo apache2ctl -SThis lists all loaded virtual hosts. If yours is missing, you forgot a2ensite or the config has a syntax error (apache2ctl configtest).
High CPU or memory usage
- Check the MPM:
apache2ctl -V | grep MPM. Switchpreforktoevent. - Lower
MaxRequestWorkersif you are OOM-killing processes. - Look for slow PHP scripts in
access.logwithawk '{print $7, $NF}' /var/log/apache2/access.log | sort | uniq -c | sort -rn | head -20.
FAQ
Is Apache still relevant in 2026?
Yes. It powers roughly a third of active websites, dominates WordPress hosting, and remains the standard for shared hosting. For PHP apps and anything needing .htaccess, Apache is often the better choice.
Should I use Apache or Nginx?
For PHP + .htaccess workloads, Apache. For static files and reverse proxying at scale, Nginx. Many sites run both.
Can I run Apache and Nginx on the same server? Yes, but not on the same port. A common setup is Nginx on :80/:443 handling SSL and static files, proxying dynamic requests to Apache on :8080 for PHP execution.
What is the best MPM for Apache?
For almost all modern workloads, event. Use prefork only if you must use mod_php and cannot switch to PHP-FPM.
How do I host multiple domains?
Create one file per domain in sites-available/, enable each with a2ensite. Each virtual host can have its own document root, SSL cert, and logs.
Do I need .htaccess files?
Only if you cannot edit the main server config (shared hosting) or you run apps that ship with one (WordPress). Otherwise, moving rules into the virtual host is faster because Apache does not re-read .htaccess on every request.
How do I enable PHP?
Install PHP-FPM (sudo apt install php-fpm) and enable proxy_fcgi + php8.3-fpm config. Avoid mod_php on modern setups.
How do I increase the upload size?
In your PHP-FPM pool (/etc/php/8.3/fpm/php.ini): upload_max_filesize = 100M and post_max_size = 100M. In Apache, also set LimitRequestBody 104857600 in your virtual host.
Next Steps
You now have a production-ready Apache web server with HTTPS, HTTP/2, compression, caching, and sensible security defaults. Where to go from here:
- Install a CMS — Deploy WordPress, Ghost, or Drupal on top of this stack.
- Add PHP-FPM — Run modern PHP applications with better performance than
mod_php. - Set up fail2ban — Block brute-force and scanning attempts against your virtual hosts.
- Configure Cloudflare — Put a CDN in front of Apache for DDoS protection and global caching.
- Monitor with
mod_status— Enable the server-status endpoint for real-time worker metrics. - Automate deployments — Ship code with Git hooks, CI/CD, or
rsync+systemctl reload apache2.
MaxRequestWorkers.Have a question or a configuration that is not covered here? Reach out to our DevOps team and we will help you tune Apache for your specific workload.