Apache HTTP Server 2.4 on Ubuntu 24.04 on Azure User Guide
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,proxyandproxy_httpturned 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.servicemakes 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.confsetsServerTokens Prod,ServerSignature OffandTraceEnable Off, so responses say onlyServer: ApacheandTRACErequests 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
noblerepository:apache2andapache2-utils - The event MPM and the
ssl,http2,rewrite,headers,proxyandproxy_httpmodules, turned on - Default virtual hosts
000-cloudimg.conf(port 80) and000-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
- An Azure subscription and an SSH public key
- For the command line route, the Azure CLI on your workstation, signed in with
az login - 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-Securityheader with theheadersmodule. - Keep applications behind Apache. Bind them to
127.0.0.1and reach them through a proxy virtual host (Step 8). - A web application firewall is not included. Ubuntu's
libapache2-mod-security2package 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.