Applications Azure

Apache HTTP Server 2.4 on Ubuntu 24.04 on Azure User Guide

| Product: Apache HTTP Server 2.4 on Ubuntu 24.04 LTS on Azure

Overview

This guide covers deploying and using Apache HTTP Server 2.4 on Ubuntu 24.04 LTS from cloudimg on Microsoft Azure.

Apache HTTP Server is a widely used open source web server. It serves static sites, hosts many sites on one server with virtual hosts, and sits in front of application servers as a reverse proxy. This image runs Apache 2.4 from Ubuntu's own noble repository (the apache2 package), so it is updated by Ubuntu like any other system package. Image version 1.0.20260425 carries Apache 2.4.58 on Ubuntu 24.04 LTS.

Every command and every output in this guide was run on a virtual machine deployed from the public Marketplace image (image version 1.0.20260425), as the azureuser account a customer gets.

What cloudimg sets up on top of the Ubuntu package:

  • Modules for modern sites. The event MPM, with ssl, http2, rewrite, headers, proxy and proxy_http turned on.
  • Two default virtual hosts. One on port 80 and one on port 443, both serving /var/www/html. Directory listings are off.
  • A certificate for each VM. At first boot, apache-firstboot.service makes a new self-signed RSA 2048-bit certificate and key for that VM, so HTTPS works from the start and no two VMs share a key. Replace it with a real certificate before you serve real visitors (Step 9).
  • A hardened Server header. /etc/apache2/conf-available/zz-security-cloudimg.conf sets ServerTokens Prod, ServerSignature Off and TraceEnable Off, so responses say only Server: Apache and TRACE requests are refused.

The document root starts empty: you add your own site (Step 5). Until you do, Apache answers 403 Forbidden for /, because there is no index page and directory listings are switched off.

What's included

  • Apache HTTP Server 2.4.58 (image version 1.0.20260425) from Ubuntu's noble repository: apache2 and apache2-utils
  • The event MPM and the ssl, http2, rewrite, headers, proxy and proxy_http modules, turned on
  • Default virtual hosts 000-cloudimg.conf (port 80) and 000-cloudimg-ssl.conf (port 443), document root /var/www/html
  • The first-boot service apache-firstboot.service, which makes the VM's own certificate in /etc/ssl/cloudimg/
  • The hardening file zz-security-cloudimg.conf
  • Ubuntu 24.04 LTS with unattended security upgrades turned on
  • Azure Linux Agent (waagent) and cloud-init for Azure provisioning
  • 24/7 cloudimg support

PHP, a WAF module and a publicly trusted certificate are not included: you add what your site needs.

Connecting to your VM

Item Value
SSH user the administrator you choose at deployment (for example azureuser)
Website http://<vm-ip>/ and https://<vm-ip>/ once your network security group allows 80 and 443
Document root /var/www/html
Configuration /etc/apache2/ (sites-available/, conf-available/, mods-available/)
Logs /var/log/apache2/
Open ports needed 22 (SSH) from your own address; 80 and 443 from the networks your visitors use

Apache has no login of its own, so the image has no web password to look up. The notes file /stage/scripts/apache-credentials.log holds a short description of the certificate set-up and no password.

Prerequisites

  1. An Azure subscription and an SSH public key
  2. For the command line route, the Azure CLI on your workstation, signed in with az login
  3. A network security group that allows TCP 22 from your own address, and TCP 80 and 443 from your visitors

Choose a virtual machine size that suits your traffic. Apache with the event MPM is light, so a small VM is a reasonable place to start, and you can resize later.

Step 1: Deploy from the Azure portal

On the listing, choose Get it now. The Azure portal then walks you through the basics: your subscription, resource group, region and size, your SSH key and inbound ports, then Review and create. For inbound ports, allow SSH (22), HTTP (80) and HTTPS (443), then narrow SSH to your own address in the network security group once the VM exists.

Step 2: Deploy from the Azure CLI

Create a resource group and a network security group that allows SSH from your own address and web traffic from everyone (for a site that only your own networks should see, put their ranges in place of Internet):

az group create --name myResourceGroup --location eastus

az network nsg create --resource-group myResourceGroup --name my-apache-nsg

az network nsg rule create --resource-group myResourceGroup --nsg-name my-apache-nsg \
  --name allow-ssh --priority 100 --protocol Tcp --access Allow \
  --source-address-prefixes <your-ip>/32 --destination-port-ranges 22

az network nsg rule create --resource-group myResourceGroup --nsg-name my-apache-nsg \
  --name allow-web --priority 110 --protocol Tcp --access Allow \
  --source-address-prefixes Internet --destination-port-ranges 80 443

The image carries a Marketplace purchase plan, so accept its terms once per subscription, then create the virtual machine. Replace <your-vm-size> with the size you chose:

az vm image terms accept --urn cloudimg1647283583153:apache-httpd-ubuntu-24-04:default:latest

az vm create \
  --resource-group myResourceGroup \
  --name my-apache-vm \
  --image cloudimg1647283583153:apache-httpd-ubuntu-24-04:default:latest \
  --size <your-vm-size> \
  --admin-username azureuser \
  --ssh-key-values ~/.ssh/id_ed25519.pub \
  --nsg my-apache-nsg \
  --public-ip-sku Standard

Step 3: Connect via SSH

Run these on your own workstation. Find the public IP, then connect:

az vm show --resource-group myResourceGroup --name my-apache-vm --show-details --query publicIps -o tsv
ssh azureuser@<vm-ip>

If you connect straight away, let first boot finish:

sudo cloud-init status --wait

It prints status: done.

Step 4: Check Apache

systemctl is-active apache2 apache-firstboot.service
apache2 -v
sudo ss -Htln '( sport = :80 or sport = :443 )' | awk '{print $4}'

Both services report active, the version line reads Server version: Apache/2.4.58 (Ubuntu), and Apache listens on *:80 and *:443.

curl -s -o /dev/null -w 'http://localhost/  -> HTTP %{http_code}\n' http://localhost/
curl -sk -o /dev/null -w 'https://localhost/ -> HTTP %{http_code}, HTTP/%{http_version}\n' https://localhost/

On a new VM both return HTTP 403, the HTTPS one over HTTP/2: Apache is answering, and the document root is still empty. The next step adds a page.

Step 5: Publish your first page

Your site's files go in /var/www/html. Write an index page and fetch it:

echo '<h1>It works on Azure</h1>' | sudo tee /var/www/html/index.html
curl -s http://localhost/
curl -sk -o /dev/null -w 'HTTP %{http_code}, HTTP/%{http_version}\n' https://localhost/

The page comes back over HTTP, and HTTPS returns HTTP 200, HTTP/2. From your workstation, open http://<vm-ip>/ in a browser. https://<vm-ip>/ works too, with a certificate warning until you install a real certificate (Step 9).

To publish a whole site, copy its files into /var/www/html (for example with scp or rsync over SSH) and keep them readable by the www-data user Apache runs as.

Step 6: How the configuration is laid out

Apache on Ubuntu keeps each site, module and configuration fragment in its own file, and turns them on with symbolic links:

Directory Holds Turn on / off
/etc/apache2/sites-available/ virtual hosts sudo a2ensite <name> / sudo a2dissite <name>
/etc/apache2/mods-available/ modules sudo a2enmod <name> / sudo a2dismod <name>
/etc/apache2/conf-available/ configuration fragments sudo a2enconf <name> / sudo a2disconf <name>

See what is on:

sudo apache2ctl -S 2>/dev/null | head -3
sudo apache2ctl -M 2>/dev/null | grep -E '^ (ssl|rewrite|headers|http2|proxy|proxy_http|mpm_event)_module'
ls /etc/apache2/conf-enabled/

apache2ctl -S lists the two cloudimg virtual hosts, *:443 from 000-cloudimg-ssl.conf and *:80 from 000-cloudimg.conf. The module list shows headers, http2, mpm_event, proxy, proxy_http, rewrite and ssl, and conf-enabled includes zz-security-cloudimg.conf.

After any change, test the configuration, then reload (no dropped connections):

sudo apache2ctl configtest
sudo systemctl reload apache2

configtest prints Syntax OK when the configuration is valid.

Step 7: Host a site with a name-based virtual host

To serve a domain from its own directory, give it a virtual host. Replace example.com with your domain:

sudo mkdir -p /var/www/example.com
echo '<h1>example.com</h1>' | sudo tee /var/www/example.com/index.html

sudo tee /etc/apache2/sites-available/example.com.conf >/dev/null <<'EOF'
<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /var/www/example.com
    <Directory /var/www/example.com>
        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>
EOF

sudo a2ensite example.com
sudo apache2ctl configtest
sudo systemctl reload apache2

Check it before your DNS points at the VM by sending the host name yourself:

curl -s -H 'Host: example.com' http://localhost/
curl -s http://localhost/

The first request gets the new site (<h1>example.com</h1>), and any other name still gets the default site in /var/www/html. Once your domain's DNS A record points at the VM's public IP, browsers reach the new site by name.

Step 8: Put Apache in front of an application

The proxy and proxy_http modules are already on, so Apache can pass requests to an application that listens on the VM, for example on 127.0.0.1:8080:

sudo tee /etc/apache2/sites-available/app.example.com.conf >/dev/null <<'EOF'
<VirtualHost *:80>
    ServerName app.example.com
    ProxyPreserveHost On
    ProxyPass        / http://127.0.0.1:8080/
    ProxyPassReverse / http://127.0.0.1:8080/
</VirtualHost>
EOF

sudo a2ensite app.example.com && sudo apache2ctl configtest && sudo systemctl reload apache2
curl -s -H 'Host: app.example.com' http://localhost/

With an application answering on port 8080, the last command returns the application's response through Apache. Keep the application on 127.0.0.1 so it is reachable only through Apache, and leave its port closed in the network security group.

Step 9: HTTPS and a real certificate

The first-boot certificate

First boot made this VM its own self-signed certificate:

sudo cat /var/log/apache-firstboot.log
sudo ls -l /etc/ssl/cloudimg/
sudo openssl x509 -in /etc/ssl/cloudimg/cloudimg.crt -noout -subject -dates -ext subjectAltName
sudo openssl x509 -in /etc/ssl/cloudimg/cloudimg.crt -noout -text | grep -m1 'Public-Key'

The log shows the certificate being generated at first boot, the key cloudimg.key is readable by root only (mode 600), the subject is CN = cloudimg-apache with the names localhost, cloudimg-apache and 127.0.0.1, it is valid for one year from first boot, and the key is Public-Key: (2048 bit). First boot runs once: a reboot keeps the same certificate.

Browsers do not trust a self-signed certificate and show a warning, so use it for testing only, and install a real certificate for your domain with one of the two options below.

Option A: a free Let's Encrypt certificate with Certbot

You need a domain whose DNS A record points at the VM's public IP, a virtual host with that ServerName (Step 7), and port 80 open to the internet (Let's Encrypt checks the domain over HTTP). Install Certbot and its Apache plugin from Ubuntu's repository:

sudo apt-get -q update
sudo apt-get install -y certbot python3-certbot-apache
certbot --version

It prints certbot 2.9.0 on Ubuntu 24.04. Then request the certificate for your names:

sudo certbot --apache -d example.com -d www.example.com

Certbot's Apache plugin proves you control the domain, installs the certificate in an HTTPS virtual host for those names, and reloads Apache. The package also turns on certbot.timer, which renews certificates before they expire:

systemctl is-enabled certbot.timer
sudo certbot renew --dry-run

Option B: your own certificate

To use a certificate from your own certificate authority, copy the certificate (with its chain) and the key onto the VM, keep the key readable by root only, and point the HTTPS virtual host at them. The default HTTPS site reads these two lines:

grep -E 'SSLCertificate(Key)?File' /etc/apache2/sites-available/000-cloudimg-ssl.conf

They read SSLCertificateFile /etc/ssl/cloudimg/cloudimg.crt and SSLCertificateKeyFile /etc/ssl/cloudimg/cloudimg.key. Change them to your files (or add the same two lines to your own *:443 virtual host), then run sudo apache2ctl configtest and sudo systemctl reload apache2.

Step 10: Check the hardening

curl -sI http://localhost/ | grep -i '^Server:'
grep -hE '^(ServerSignature|TraceEnable)' /etc/apache2/conf-enabled/zz-security-cloudimg.conf
curl -s -o /dev/null -w 'TRACE -> HTTP %{http_code}\n' -X TRACE http://localhost/

The header reads Server: Apache, with no version or operating system, the file shows ServerSignature Off and TraceEnable Off, and a TRACE request gets HTTP 405. The zz- prefix makes the file load after Ubuntu's own security.conf, so its settings win: keep the name.

Step 11: Keep it updated and running

The image was patched when it was built. Ubuntu security updates install automatically (unattended-upgrades is enabled), and other updates do not, so update a new VM before you put it into service, and regularly afterwards:

sudo apt-get -q update
sudo apt-get -y upgrade

Apache is an Ubuntu package, so Apache updates arrive this way too. Reboot if a new kernel was installed.

Everyday service commands:

sudo systemctl status apache2 --no-pager
sudo systemctl reload apache2
sudo systemctl restart apache2
sudo tail -n 50 /var/log/apache2/error.log
sudo tail -n 50 /var/log/apache2/access.log

The default HTTPS site logs to error-ssl.log and access-ssl.log in the same directory, and Ubuntu's logrotate rotates the Apache logs. Before a big change, take a snapshot of the OS disk, and back up /etc/apache2/ and your sites under /var/www/.

Security notes

  • Network security group. Allow SSH (22) from your own address only. Open 80 and 443 to the networks your visitors use. The Ubuntu firewall (ufw) is installed but inactive, so the network security group is what filters traffic.
  • Use a real certificate (Step 9) before you serve real visitors. Once HTTPS works with it, you can redirect HTTP to HTTPS and add a Strict-Transport-Security header with the headers module.
  • Keep applications behind Apache. Bind them to 127.0.0.1 and reach them through a proxy virtual host (Step 8).
  • A web application firewall is not included. Ubuntu's libapache2-mod-security2 package adds ModSecurity if you need one.

When you have finished with the examples, remove them:

sudo a2dissite example.com app.example.com
sudo systemctl reload apache2
sudo rm -r /var/www/example.com /etc/apache2/sites-available/example.com.conf /etc/apache2/sites-available/app.example.com.conf

Server components

Component Path
Apache daemon /usr/sbin/apache2 (apache2.service)
Main configuration /etc/apache2/apache2.conf
Default virtual hosts /etc/apache2/sites-available/000-cloudimg.conf, 000-cloudimg-ssl.conf
Hardening /etc/apache2/conf-available/zz-security-cloudimg.conf
Document root /var/www/html
First-boot certificate and key /etc/ssl/cloudimg/cloudimg.crt, cloudimg.key
First-boot service and log apache-firstboot.service, /usr/local/sbin/apache-firstboot.sh, /var/log/apache-firstboot.log
Logs /var/log/apache2/

Support and licensing

Apache HTTP Server is licensed under the Apache License 2.0. cloudimg provides round-the-clock technical support for this image by email, with a guaranteed 24-hour response time: support@cloudimg.co.uk.

Apache, Apache HTTP Server and the Apache feather logo are trademarks of The Apache Software Foundation. Ubuntu is a registered trademark of Canonical Ltd. cloudimg is not affiliated with or endorsed by either of them.