Start with a working server and a domain you control
Continue after the Apache installation guide, using your own Ubuntu 24.04 LTS or AlmaLinux 9 VPS. Apache should already respond over HTTP, your non-root account should have tested sudo access, and your recovery console should be available. You also need permission to edit the domain's authoritative DNS records.
Replace every example.com with your registered domain, including filenames and directory paths. Replace 203.0.113.10 with your server's public IPv4 address. Both values are documentation examples; do not publish them unchanged. Use a new domain-specific directory and configuration file, and check that they do not belong to an existing site before editing.
cat /etc/os-release
sudo -v
sudo ss -lntpCreate DNS records for the apex and www
At the provider hosting your authoritative DNS zone, create an A record for the apex, normally entered as @ or example.com, pointing to your real public IPv4 address. Add a CNAME for www pointing to example.com. An A record for www pointing to the same IPv4 address is another valid choice; do not combine a CNAME with other record types at the same name.
Add an AAAA record only when the server has working public IPv6, Apache listens for it and the firewall permits it. A stale AAAA pointing elsewhere can break certificate validation even when IPv4 works. Review existing records before replacing them, and leave unrelated mail records intact. Cached answers may remain until their previous TTL expires.
For a new practice site using Cloudflare, begin with these records set to DNS only so you can verify the VPS directly. Do not disable proxying on an established protected site merely to follow this guide; use the proxy-specific section below.
sudo apt update && sudo apt install dnsutils curlsudo dnf install bind-utils curldig +short A example.com
dig +short AAAA example.com
dig +short CNAME www.example.com
dig +short A www.example.com
dig +short CAA example.comAllow web traffic without disturbing SSH
HTTP uses TCP 80 and HTTPS uses TCP 443. Check both the VPS firewall and any provider firewall or security group. Keep your working SSH session open and retain the rule for the actual SSH port. This section adds web rules to an already configured firewall; it does not reset rules or enable an unprepared firewall.
Use the Ubuntu branch for an active UFW firewall. For an active AlmaLinux firewalld service, first inspect the active zones. Replace YOUR_ACTIVE_ZONE with the zone attached to the public network interface before adding rules; runtime and permanent rules are updated separately.
sudo ufw status verbose
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status numberedsudo firewall-cmd --get-active-zonesZONE=YOUR_ACTIVE_ZONE
sudo firewall-cmd --zone="$ZONE" --add-service=http
sudo firewall-cmd --zone="$ZONE" --add-service=https
sudo firewall-cmd --permanent --zone="$ZONE" --add-service=http
sudo firewall-cmd --permanent --zone="$ZONE" --add-service=https
sudo firewall-cmd --zone="$ZONE" --list-allCreate a small domain-specific document root
Keep the new site separate from the default Apache page. Create its document root with ordinary read/traverse permissions, then use sudoedit to make an index.html file. If either path already contains a site, stop and choose another domain or back up and review that site before editing. Do not grant the Apache account write access to all website files.
Put the small HTML example into the editor, save and exit. A static page normally needs mode 644; the directory needs mode 755 so Apache can traverse it. On AlmaLinux, restore the standard SELinux labels under /var/www rather than turning SELinux off.
sudo install -d -m 755 /var/www/example.com/html
sudoedit /var/www/example.com/html/index.html<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>My first HTTPS site</title>
<h1>My domain reaches this VPS.</h1>
</html>sudo chmod 644 /var/www/example.com/html/index.htmlsudo restorecon -RFv /var/www/example.com
ls -Zd /var/www/example.com/html
ls -Z /var/www/example.com/html/index.htmlUbuntu: add and validate the named virtual host
On Ubuntu, site definitions live in /etc/apache2/sites-available and a2ensite enables them. Open a new example.com.conf with sudoedit, replacing the domain in its filename. Paste the configuration below with the same domain and document-root substitutions. ServerName matches the apex; ServerAlias accepts www.
Save the file, enable that site and inspect the virtual-host list. Reload only after the configuration test succeeds. Keep unrelated site definitions and the default site intact; they do not have to be deleted for a correctly named host to work.
sudoedit /etc/apache2/sites-available/example.com.conf<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com/html
<Directory /var/www/example.com/html>
Options -Indexes
AllowOverride None
Require all granted
</Directory>
ErrorLog /var/log/apache2/example.com-error.log
CustomLog /var/log/apache2/example.com-access.log combined
</VirtualHost>sudo a2ensite example.com.conf
sudo apache2ctl -S
sudo apache2ctl configtest && sudo systemctl reload apache2AlmaLinux: add and validate the named virtual host
On AlmaLinux, put the new domain's definition in /etc/httpd/conf.d/example.com.conf. Files ending in .conf are included automatically; there is no a2ensite step. Use the following configuration for AlmaLinux instead of the Ubuntu file.
Inspect the virtual-host mapping and validate before reloading httpd. If syntax validation fails, correct the new file and run the test again. Do not restart repeatedly or remove existing host definitions to hide an error.
sudoedit /etc/httpd/conf.d/example.com.conf<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com/html
<Directory /var/www/example.com/html>
Options -Indexes
AllowOverride None
Require all granted
</Directory>
ErrorLog /var/log/httpd/example.com-error.log
CustomLog /var/log/httpd/example.com-access.log combined
</VirtualHost>sudo httpd -S
sudo apachectl configtest && sudo systemctl reload httpdVerify HTTP from outside the VPS
Run these checks from your own computer or another network using curl. Replace both the domain and the documentation IP. The --resolve check reaches the chosen address while still sending the correct host name; it helps separate Apache routing from DNS caching.
Expect your new page from both host names over ordinary HTTP before requesting a certificate. A successful local request alone does not prove the provider firewall or public DNS is correct. If you published IPv6, verify that path too. Do not proceed while either name returns an unrelated site, authentication challenge or error.
curl --resolve example.com:80:203.0.113.10 http://example.com/
curl -I http://example.com/
curl -I http://www.example.com/curl -6 -I http://example.com/
curl -6 -I http://www.example.com/Install one Certbot distribution and its Apache plugin
These examples use distribution packages so updates and renewal services stay with APT or DNF. Do not mix package, snap and Python installations of Certbot. If command -v certbot already shows another installation, follow that installation's official maintenance instructions instead of adding a second copy.
Ubuntu provides Certbot and its Apache plugin in the Universe repository. AlmaLinux uses Fedora EPEL; CRB supplies dependencies. On AlmaLinux, mod_ssl enables Apache's TLS support. Review the package manager's proposed changes before accepting.
After installing mod_ssl, initialize its package-provided default certificate before running Apache configuration checks. This localhost certificate is only for the default site; Certbot will obtain a certificate for your named domain in the next step.
command -v certbotsudo apt update && sudo apt install certbot python3-certbot-apachesudo dnf install dnf-plugins-core
sudo dnf config-manager --set-enabled crb
sudo dnf install epel-release
sudo dnf install certbot python3-certbot-apache mod_sslsudo systemctl start httpd-init.service
sudo apachectl configtestcertbot --version
sudo certbot pluginsIssue a certificate covering both names
Before issuing, choose the branch that matches your DNS setup. The --redirect example is for the new DNS-only practice site. If this domain is already proxied through Cloudflare, review the proxy-specific section before running Certbot. In particular, a site using Flexible mode must use --no-redirect and keep origin HTTP working while HTTPS is added; an origin HTTP-to-HTTPS redirect can otherwise cause a loop.
Back up Apache's configuration before allowing Certbot's Apache plugin to edit it. The backup command uses a new dated directory and stops if that directory already exists. Use only the backup branch for your operating system, then run only one of the two issuance branches below.
Replace both domain names in your chosen Certbot command. Read the account prompts and terms; supply an email address you control when requested. The Apache plugin proves domain control using HTTP-01 and configures HTTPS. Both names must reach the correct challenge response on public port 80 during validation. This method does not issue wildcard certificates.
For an existing Flexible-mode site, do not change the zone's encryption mode or add an origin redirect during this step. First verify origin HTTPS directly, then have the zone owner review every affected hostname and origin for compatibility before considering Full (strict). If the other origins cannot support that mode, retain the current setting and plan the transition separately.
BACKUP_DIR="/root/apache2-before-https-$(date -u +%Y%m%dT%H%M%SZ)"
sudo mkdir "$BACKUP_DIR" && sudo cp -a /etc/apache2/. "$BACKUP_DIR/"BACKUP_DIR="/root/httpd-before-https-$(date -u +%Y%m%dT%H%M%SZ)"
sudo mkdir "$BACKUP_DIR" && sudo cp -a /etc/httpd/. "$BACKUP_DIR/"sudo certbot --apache -d example.com -d www.example.com --redirectsudo certbot --apache -d example.com -d www.example.com --no-redirectsudo certbot certificatesCheck HTTPS and the redirect
From outside the VPS, test HTTPS for both names and inspect the HTTP response. Curl should validate the certificate without -k; ignoring validation would hide an expired, untrusted or mismatched certificate. Open each URL in a browser and check the page content as well as the certificate details.
For the DNS-only branch, HTTP should redirect to HTTPS. That redirect changes the scheme; it does not necessarily choose between apex and www. Both names remain valid because the certificate includes both. Choose a single preferred host separately if your site requires it, and keep images, scripts and styles on HTTPS to avoid mixed content.
For an existing proxied Flexible site, the public HTTPS response checks Cloudflare's edge rather than the VPS certificate. Use the direct-origin HTTPS checks in the proxy section below. The --no-redirect branch intentionally leaves origin HTTP available; do not expect its direct HTTP response to redirect until a compatible proxy-to-origin HTTPS setup has been verified.
curl -I https://example.com/
curl -I https://www.example.com/
curl -IL http://example.com/sudo apache2ctl configtest
sudo journalctl -u apache2 -n 30 --no-pagersudo apachectl configtest
sudo journalctl -u httpd -n 30 --no-pagerConfirm renewal before you forget about the certificate
Run a renewal dry run, which tests against the certificate authority's staging service. A successful first issuance does not guarantee later renewals if DNS, firewalls or challenge paths change. Preserve HTTP-01 access on port 80 unless you deliberately migrate the renewal method.
The Ubuntu package uses certbot.timer; the AlmaLinux EPEL package provides certbot-renew.timer. Enable the relevant timer and inspect its next scheduled run. Keep Certbot and Apache updated through the same package manager. Inspect renewal-service failures as part of ordinary server maintenance; do not hard-code a certificate lifetime or assume expiry monitoring exists.
sudo certbot renew --dry-runsudo systemctl enable --now certbot.timer
systemctl list-timers --all certbot.timer
sudo journalctl -u certbot.service -n 30 --no-pagersudo systemctl enable --now certbot-renew.timer
systemctl list-timers --all certbot-renew.timer
sudo journalctl -u certbot-renew.service -n 30 --no-pagerIf you want to keep Cloudflare proxying enabled
With a proxied record, public DNS returns Cloudflare addresses and the browser sees Cloudflare's edge certificate. That browser certificate alone does not verify the VPS certificate. After issuing your origin certificate, test the origin directly using --resolve and the real VPS address from a network allowed to reach it.
HTTP-01 can work through a proxy only when every requested name's /.well-known/acme-challenge/ path reaches the correct challenge response. Access login screens, bot challenges, caching and redirect rules can interfere. An existing Flexible-mode site must follow the --no-redirect issuance branch above. Verify HTTPS directly at the origin before the zone owner considers a mode change; review every affected hostname and origin, and retain the current mode if compatibility is not established. For the new DNS-only practice site, complete the direct HTTPS checks before enabling proxying and choosing a compatible Full (strict) setup.
If an established site cannot expose a reliable HTTP challenge path, use automated DNS-01 with a Certbot plugin for your DNS provider. DNS-01 validates TXT records under _acme-challenge, supports wildcards and needs no inbound HTTP validation port. It does not configure Apache or make HTTPS reachable by itself. Use the plugin's documented Apache installation option and a renewal-capable setup.
For Cloudflare's DNS plugin, prefer a narrowly scoped API token with DNS edit permission only for the required zone, store its credentials file outside the web root with mode 600, and follow the plugin's official instructions. A manual one-off TXT challenge will not renew unattended without automation. Re-run the renewal dry run after changing challenge methods or proxy settings.
curl --resolve example.com:443:203.0.113.10 -I https://example.com/
curl --resolve www.example.com:443:203.0.113.10 -I https://www.example.com/Diagnose problems one layer at a time
An unexpected default page usually means the requested host name does not match the enabled virtual host; inspect apache2ctl -S or httpd -S. A connection timeout points to DNS, routing, listening addresses or firewall rules. A 403 suggests filesystem permissions, the Directory rule or AlmaLinux SELinux labels. Inspect the domain's error log before changing permissions.
For certificate validation failures, check apex and www separately, remove or correct unintended stale AAAA records, and inspect restrictive CAA policy and proxy challenge handling. For renewal failures, read the renewal service log and repeat the dry run after resolving the cause. Back up your site and configuration independently, and test a restore before putting irreplaceable content on the server.
sudo tail -n 50 /var/log/apache2/example.com-error.logsudo tail -n 50 /var/log/httpd/example.com-error.logOfficial documentation
These upstream references provide more detail on the commands and workflows in this guide.
- Apache HTTP Server: name-based virtual hosts (opens in a new tab)
- Ubuntu Server: Apache2 settings (opens in a new tab)
- RHEL 9: Apache configuration, virtual hosts and SELinux (opens in a new tab)
- Ubuntu Noble: Certbot Apache plugin package (opens in a new tab)
- AlmaLinux: EPEL and CRB repositories (opens in a new tab)
- Fedora EPEL 9: Certbot package and renewal timer (opens in a new tab)
- Certbot: certificates, Apache integration and renewal (opens in a new tab)
- Let's Encrypt: HTTP-01 and DNS-01 challenges (opens in a new tab)
- Let's Encrypt: IPv6 validation behaviour (opens in a new tab)
- Let's Encrypt: Certificate Authority Authorization (opens in a new tab)
- Cloudflare: manage DNS records (opens in a new tab)
- Cloudflare: DNS proxy status (opens in a new tab)
- Cloudflare: Full (strict) encryption requirements (opens in a new tab)
- Certbot Cloudflare DNS plugin: credentials and automation (opens in a new tab)