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 sudo privileges.
  • A domain name with an A record pointing to your server's public IP address. This guide uses your_domain as 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:

PathPurpose
/etc/apache2/apache2.confMain configuration file. Rarely needs changes.
/etc/apache2/ports.confPorts 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

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:

ItemUbuntu 24.04Rocky Linux 9
Package and serviceapache2httpd (sudo dnf install httpd, then sudo systemctl enable --now httpd)
Main config/etc/apache2/apache2.conf/etc/httpd/conf/httpd.conf
Virtual hostssites-available/ + a2ensiteAny *.conf file in /etc/httpd/conf.d/
Modulesa2enmodLoadModule lines in /etc/httpd/conf.modules.d/
Logs/var/log/apache2//var/log/httpd/
Config testapache2ctl configtestapachectl configtest
Firewallufw allow 'Apache Full'firewall-cmd --permanent --add-service=http --add-service=https and firewall-cmd --reload
HTTPS moduleincludedsudo 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.