Apache HTTP Server is a mature, modular web server that powers a large share of the web and remains the default choice for PHP applications and anything that relies on .htaccess files. In this tutorial you will install Apache on Ubuntu 24.04, allow it through the firewall, serve your own site from a virtual host, enable the modules most sites need, tighten a few insecure defaults and secure the site with a Let's Encrypt certificate. Differences for Rocky Linux 9 are summarized at the end.
Prerequisites
To follow this guide you need:
- A server running Ubuntu 24.04 LTS, for example a CubePath VPS.
- A non-root user with
sudoprivileges. - A domain name with an
Arecord pointing to your server's public IP address. This guide usesyour_domainas a placeholder. The domain is only required for the HTTPS step. - Ports 80 and 443 reachable from the internet.
Step 1 - Installing Apache
Apache is packaged in Ubuntu's main repository as apache2. Refresh the package index and install it:
sudo apt update
sudo apt install apache2
The package starts the service and enables it at boot automatically. Confirm that it is running:
sudo systemctl status apache2
● apache2.service - The Apache HTTP Server
Loaded: loaded (/usr/lib/systemd/system/apache2.service; enabled; preset: enabled)
Active: active (running) since Thu 2026-09-24 10:12:03 UTC; 15s ago
Press q to leave the status view. Check the installed version:
apache2 -v
Server version: Apache/2.4.58 (Ubuntu)
Server built: 2025-08-11T11:10:09
The exact build date depends on the latest security update.
Step 2 - Allowing Apache through the firewall
Apache registers application profiles with UFW when it is installed. List them:
sudo ufw app list
Available applications:
Apache
Apache Full
Apache Secure
OpenSSH
Apache opens port 80, Apache Secure opens port 443 and Apache Full opens both. Since you will add HTTPS later, allow Apache Full:
sudo ufw allow 'Apache Full'
If UFW is not active yet, allow SSH first so you do not lock yourself out, then enable it:
sudo ufw allow OpenSSH
sudo ufw enable
Verify the rules:
sudo ufw status
Status: active
To Action From
-- ------ ----
OpenSSH ALLOW Anywhere
Apache Full ALLOW Anywhere
OpenSSH (v6) ALLOW Anywhere (v6)
Apache Full (v6) ALLOW Anywhere (v6)
Now request the default page from the server itself:
curl -I http://localhost
HTTP/1.1 200 OK
Server: Apache/2.4.58 (Ubuntu)
Content-Type: text/html
You can also open http://your_server_ip in a browser, where you will see the "Apache2 Default Page".
Step 3 - Understanding the configuration layout
Ubuntu splits Apache's configuration into small files that you enable and disable with helper commands instead of editing one large file:
| Path | Purpose |
|---|---|
/etc/apache2/apache2.conf | Main configuration file. Rarely needs changes. |
/etc/apache2/ports.conf | Ports Apache listens on (80, and 443 when mod_ssl is enabled). |
/etc/apache2/sites-available/ | One file per virtual host. Enabled with a2ensite. |
/etc/apache2/conf-available/ | Global configuration snippets. Enabled with a2enconf. |
/etc/apache2/mods-available/ | Module load and config files. Enabled with a2enmod. |
/var/www/html/ | Document root of the default site. |
/var/log/apache2/ | access.log and error.log. |
The *-enabled directories contain symbolic links created by the a2en* commands. Each helper has a matching a2dis* command to disable an item.
Before you change anything, get into the habit of testing the configuration and reloading instead of restarting, so a typo never takes the site down:
sudo apache2ctl configtest
sudo systemctl reload apache2
Step 4 - Setting a global ServerName
apache2ctl configtest usually prints a warning on a fresh install:
AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 127.0.1.1. Set the 'ServerName' directive globally to suppress this message
Syntax OK
It is harmless, but it hides real warnings. Create a small configuration snippet:
sudo nano /etc/apache2/conf-available/servername.conf
ServerName localhost
Enable it and test again:
sudo a2enconf servername
sudo apache2ctl configtest
Syntax OK
Step 5 - Creating a virtual host for your site
Instead of replacing the default page, give your site its own document root and virtual host. Create the directory and hand it to your user so you can upload files without sudo:
sudo mkdir -p /var/www/your_domain/html
sudo chown -R "$USER":"$USER" /var/www/your_domain/html
Add a test page:
nano /var/www/your_domain/html/index.html
<!DOCTYPE html>
<html>
<head><title>your_domain</title></head>
<body><h1>It works: your_domain is served by Apache</h1></body>
</html>
Create the virtual host file:
sudo nano /etc/apache2/sites-available/your_domain.conf
<VirtualHost *:80>
ServerName your_domain
ServerAlias www.your_domain
ServerAdmin webmaster@your_domain
DocumentRoot /var/www/your_domain/html
<Directory /var/www/your_domain/html>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/your_domain_error.log
CustomLog ${APACHE_LOG_DIR}/your_domain_access.log combined
</VirtualHost>
Options -Indexes prevents Apache from listing directory contents when there is no index file, and AllowOverride All lets applications such as WordPress use .htaccess files. If you do not need .htaccess, set it to None, which is slightly faster.
Enable the new site, disable the default one and reload:
sudo a2ensite your_domain.conf
sudo a2dissite 000-default.conf
sudo apache2ctl configtest
sudo systemctl reload apache2
Verify that Apache answers with your page. The Host header lets you test before DNS has propagated:
curl -H "Host: your_domain" http://localhost
<!DOCTYPE html>
<html>
<head><title>your_domain</title></head>
<body><h1>It works: your_domain is served by Apache</h1></body>
</html>
To host several domains on the same server, repeat this step for each one. The virtual hosts guide covers that in depth.
Step 6 - Enabling common modules
Most sites need a few modules that are installed but not enabled by default:
rewrite: URL rewriting, required by most CMS and frameworks.headers: setting HTTP response headers such as security headers.ssl: HTTPS support (Certbot enables it for you in Step 8).
Enable rewrite and headers:
sudo a2enmod rewrite headers
sudo systemctl restart apache2
Loading a module requires a restart rather than a reload. Confirm that they are active:
apache2ctl -M | grep -E 'rewrite|headers'
headers_module (shared)
rewrite_module (shared)
Compression (deflate) is already enabled on Ubuntu, so text responses are gzipped out of the box.
Ubuntu uses the event MPM by default, which handles many concurrent connections with little memory. Check it with:
apache2ctl -V | grep -i mpm
Server MPM: event
NoteInstalling
libapache2-mod-phpswitches Apache to the olderpreforkMPM becausemod_phpis not thread-safe. For PHP sites, prefer PHP-FPM with theeventMPM (sudo apt install php-fpm, thensudo a2enmod proxy_fcgi setenvifandsudo a2enconf php8.3-fpm).
Step 7 - Hardening the default configuration
By default Apache reveals its version and the operating system in the Server header and on error pages. That information helps attackers match known vulnerabilities. Open the security snippet that ships with Ubuntu:
sudo nano /etc/apache2/conf-available/security.conf
Find the existing ServerTokens and ServerSignature lines and change them to:
ServerTokens Prod
ServerSignature Off
TraceEnable Off
Then add a small set of security headers that apply to every site. Create a new snippet:
sudo nano /etc/apache2/conf-available/security-headers.conf
<IfModule mod_headers.c>
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"
</IfModule>
Enable it, test and reload:
sudo a2enconf security-headers
sudo apache2ctl configtest
sudo systemctl reload apache2
Check the response headers:
curl -I -H "Host: your_domain" http://localhost
HTTP/1.1 200 OK
Server: Apache
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin-when-cross-origin
Content-Type: text/html
The Server header now shows only Apache, without version or OS.
Step 8 - Securing the site with Let's Encrypt
With DNS pointing to your server, install Certbot and its Apache plugin:
sudo apt install certbot python3-certbot-apache
Request a certificate for both names in your virtual host:
sudo certbot --apache -d your_domain -d www.your_domain
Certbot asks for an email address for expiry notices and for acceptance of the terms of service. It then validates the domain, enables mod_ssl, creates /etc/apache2/sites-available/your_domain-le-ssl.conf and adds a redirect from HTTP to HTTPS.
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/your_domain/fullchain.pem
Key is saved at: /etc/letsencrypt/live/your_domain/privkey.pem
...
Congratulations! You have successfully enabled HTTPS on https://your_domain and https://www.your_domain
Certificates are renewed automatically by a systemd timer. Confirm that renewal works:
sudo certbot renew --dry-run
Congratulations, all simulated renewals succeeded:
/etc/letsencrypt/live/your_domain/fullchain.pem (success)
Open https://your_domain in a browser to confirm the padlock.
Step 9 - Reading the logs
Each virtual host writes to its own log files, defined in Step 5. Follow them live while you load the site:
sudo tail -f /var/log/apache2/your_domain_access.log /var/log/apache2/your_domain_error.log
Service-level problems, such as Apache failing to start, appear in the journal:
sudo journalctl -u apache2 --since "1 hour ago"
Log rotation is already configured in /etc/logrotate.d/apache2: logs are rotated daily, compressed and kept for 14 days.
Rocky Linux 9 differences
Apache works the same way on Rocky Linux 9 and AlmaLinux 9, but the packaging differs:
| Item | Ubuntu 24.04 | Rocky Linux 9 |
|---|---|---|
| Package and service | apache2 | httpd (sudo dnf install httpd, then sudo systemctl enable --now httpd) |
| Main config | /etc/apache2/apache2.conf | /etc/httpd/conf/httpd.conf |
| Virtual hosts | sites-available/ + a2ensite | Any *.conf file in /etc/httpd/conf.d/ |
| Modules | a2enmod | LoadModule lines in /etc/httpd/conf.modules.d/ |
| Logs | /var/log/apache2/ | /var/log/httpd/ |
| Config test | apache2ctl configtest | apachectl configtest |
| Firewall | ufw allow 'Apache Full' | firewall-cmd --permanent --add-service=http --add-service=https and firewall-cmd --reload |
| HTTPS module | included | sudo dnf install mod_ssl |
On Rocky Linux, SELinux is enforcing. Keep site content under /var/www/ so it inherits the correct httpd_sys_content_t label, and run sudo restorecon -Rv /var/www/your_domain after moving files into place.
Troubleshooting
Apache fails to start after a change. Run sudo apache2ctl configtest; it prints the file and line of the syntax error. Then check sudo journalctl -u apache2 -n 50.
Address already in use: AH00072: make_sock: could not bind to address [::]:80. Another process, often Nginx, holds port 80. Find it with sudo ss -tlnp | grep ':80 ' and stop or reconfigure it.
403 Forbidden. Either the <Directory> block lacks Require all granted, there is no index file and -Indexes is set, or Apache cannot read the files. Apache runs as www-data, which needs read access to the files and execute access on every parent directory: namei -l /var/www/your_domain/html/index.html shows where access breaks.
500 Internal Server Error with .htaccess. The error log usually shows Invalid command 'RewriteEngine', which means mod_rewrite is not enabled. Run sudo a2enmod rewrite and restart Apache.
Conclusion
You now have Apache running on Ubuntu 24.04 with its own virtual host, the rewrite and headers modules enabled, version information hidden, basic security headers and an auto-renewing HTTPS certificate. Good next steps are hosting additional domains with Apache virtual hosts, adding PHP-FPM for dynamic applications, and reading the Apache vs Nginx comparison if you are still deciding which server fits your workload.
