Web hostingWritten by GLWS35 min readUbuntu 24.04 LTS / AlmaLinux 9
Commands run on your server unless a section says otherwise. Replace example usernames, addresses and paths with your own values. Keep a working session open when changing remote access.

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.

On the VPS: check the operating system, current listener and sudo access
cat /etc/os-release
sudo -v
sudo ss -lntp
These instructions configure your own server. Managed panels may generate Apache configuration automatically; use that panel's domain and certificate workflow rather than editing files it owns.

Create 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.

Ubuntu: install DNS lookup tools and curl
sudo apt update && sudo apt install dnsutils curl
AlmaLinux: install DNS lookup tools and curl
sudo dnf install bind-utils curl
Check the public DNS answers after replacing the sample domain
dig +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.com
An empty AAAA answer is fine when you are serving only IPv4. If an existing CAA policy restricts issuers, check that it permits Let's Encrypt through letsencrypt.org; do not delete a deliberate certificate policy.

Allow 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.

Ubuntu: inspect UFW and add HTTP/HTTPS
sudo ufw status verbose
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status numbered
AlmaLinux: identify the public interface's active zone
sudo firewall-cmd --get-active-zones
AlmaLinux: set the reviewed zone and add web services
ZONE=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-all
If your firewall is inactive or unfamiliar, complete the firewall guide first. Do not start a firewall without allowing and testing your existing SSH access.

Create 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.

On your VPS, replacing the domain in both paths
sudo install -d -m 755 /var/www/example.com/html
sudoedit /var/www/example.com/html/index.html
HTML to place in index.html; this is file content, not a shell command
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>My first HTTPS site</title>
<h1>My domain reaches this VPS.</h1>
</html>
After saving the file
sudo chmod 644 /var/www/example.com/html/index.html
AlmaLinux only: restore and inspect content labels
sudo restorecon -RFv /var/www/example.com
ls -Zd /var/www/example.com/html
ls -Z /var/www/example.com/html/index.html
The expected AlmaLinux static-content type is httpd_sys_content_t. A document root outside /var/www needs an appropriate persistent SELinux file-context rule, which is outside this beginner example.

Ubuntu: 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.

Ubuntu: open the new site definition
sudoedit /etc/apache2/sites-available/example.com.conf
Ubuntu configuration file content
<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>
Ubuntu: enable, check and reload only on success
sudo a2ensite example.com.conf
sudo apache2ctl -S
sudo apache2ctl configtest && sudo systemctl reload apache2

AlmaLinux: 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.

AlmaLinux: open the new site definition
sudoedit /etc/httpd/conf.d/example.com.conf
AlmaLinux configuration file content
<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>
AlmaLinux: check and reload only on success
sudo httpd -S
sudo apachectl configtest && sudo systemctl reload httpd

Verify 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.

Outside the VPS: inspect the content and public responses
curl --resolve example.com:80:203.0.113.10 http://example.com/
curl -I http://example.com/
curl -I http://www.example.com/
Only if you published working AAAA records
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.

Check for an existing installation first
command -v certbot
Ubuntu 24.04 LTS
sudo apt update && sudo apt install certbot python3-certbot-apache
AlmaLinux 9
sudo 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_ssl
AlmaLinux only: initialize the package default TLS certificate, then validate
sudo systemctl start httpd-init.service
sudo apachectl configtest
Confirm the installed version and available Apache plugin
certbot --version
sudo certbot plugins
If a package is unavailable, review the enabled official repositories for your distribution. Certbot's site also documents a snap installation; use that complete method rather than downloading an unverified installer.

Issue 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.

Ubuntu: create a configuration backup
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/"
AlmaLinux: create a configuration backup
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/"
New DNS-only practice site: issue and add the HTTPS redirect
sudo certbot --apache -d example.com -d www.example.com --redirect
Existing proxied Flexible site only: issue without an origin redirect
sudo certbot --apache -d example.com -d www.example.com --no-redirect
Inspect the issued certificate after the selected branch succeeds
sudo certbot certificates
If issuance fails, read the reported challenge error before retrying. Repeated unchanged requests can encounter certificate-authority rate limits. Your domain and all published addresses must reach the intended host.

Check 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.

Outside the VPS
curl -I https://example.com/
curl -I https://www.example.com/
curl -IL http://example.com/
Ubuntu: validate and inspect current service messages
sudo apache2ctl configtest
sudo journalctl -u apache2 -n 30 --no-pager
AlmaLinux: validate and inspect current service messages
sudo apachectl configtest
sudo journalctl -u httpd -n 30 --no-pager

Confirm 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.

Test renewal on your VPS
sudo certbot renew --dry-run
Ubuntu package timer
sudo systemctl enable --now certbot.timer
systemctl list-timers --all certbot.timer
sudo journalctl -u certbot.service -n 30 --no-pager
AlmaLinux EPEL package timer
sudo systemctl enable --now certbot-renew.timer
systemctl list-timers --all certbot-renew.timer
sudo journalctl -u certbot-renew.service -n 30 --no-pager

If 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.

Outside the VPS: validate the origin certificate directly
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/
Full (strict) requires an origin that accepts HTTPS and presents an unexpired certificate for the requested name. Its selection must account for all affected origins. Flexible mode plus an origin HTTP-to-HTTPS redirect can create a redirect loop; --no-redirect adds no such redirect and does not remove one that already exists.

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.

Ubuntu: inspect the named site's error log
sudo tail -n 50 /var/log/apache2/example.com-error.log
AlmaLinux: inspect the named site's error log
sudo tail -n 50 /var/log/httpd/example.com-error.log

Official documentation

These upstream references provide more detail on the commands and workflows in this guide.

Your first VPS: Next lesson: Make an independent backup and test a restore. View all nine lessons.