All virtual hosts built since Chapter 12, namely example.local, www.example.local, blog.example.local in Nginx and Apache, and app.example.local in Chapter 16, still serve traffic over plain HTTP. Every request and response between Visitor and server runs as plain text readable by anyone successfully eavesdropping on the network path between them. On shared office WiFi networks, public hotspots, or even wired networks with a compromised device, an Attacker simply needs to run a packet sniffer like Wireshark or tcpdump to read the request payload as-is, including login passwords sent via HTML forms or session cookies that should remain confidential.
This chapter closes that vulnerability by adding a TLS (Transport Layer Security) layer to all built virtual hosts. We will start from basic concepts of HTTPS and digital certificates, followed by Certbot and Let's Encrypt as the most common method to obtain free certificates for actual public domains, then directly practice TLS configuration in Nginx and Apache using lab certificates, and conclude with auto-renewal strategies so certificates never expire unnoticed.
17.1 HTTPS Concepts and Digital Certificates
HTTPS is essentially the exact same HTTP protocol used since Chapter 12, except all its data is wrapped via TLS before transmission across the network. The older name for this protocol is SSL (Secure Sockets Layer), but all SSL versions (2.0 and 3.0) have been declared deprecated and insecure for many years. The term "SSL" persists in everyday conversations including this chapter title, but actual implementations running on any modern server, including the ones configured in this chapter, are TLS versions 1.2 and 1.3.
17.1.1 Encryption and TLS Handshake in Brief
TLS combines two types of encryption simultaneously. Asymmetric encryption, utilizing a pair of public and private keys, is used at the start of a connection to mutually verify identity and agree upon a shared secret key, while symmetric encryption, which is computationally much faster, is used to encrypt actual data throughout the connection session. This negotiation process at connection startup is called a TLS handshake: the client sends a list of supported ciphers via a ClientHello message, the server responds by selecting a cipher and sending its certificate via a ServerHello message, both parties then compute the same session key without ever sending the private key across the network, and only after that does actual application data begin flowing in an encrypted state.
The full details of this handshake changed significantly between TLS 1.2 and TLS 1.3, particularly regarding the number of round-trips required before data transmission begins, but for Sysadmins managing web servers, these details are handled entirely by underlying TLS libraries such as OpenSSL. Our job is simply to provide a valid certificate and enable secure protocol versions, steps to be practiced directly in Section 17.3.
17.1.2 X.509 Certificate Anatomy and Chain of Trust
Digital certificates used by HTTPS follow the X.509 standard, a format binding a domain identity to a public key, digitally signed by the issuing authority. The most important fields within this certificate include Subject (certificate owner identity, typically the domain name), Issuer (the party issuing and signing the certificate), Validity Period (the Not Before and Not After ranges determining active periods), Public Key, and the Issuer's digital signature at the end. In recent years, modern browsers like Chrome and Firefox no longer trust the Common Name (CN) field alone, requiring registered domain names within the Subject Alternative Name (SAN) extension, which also allows a single certificate to cover multiple domains simultaneously: a feature directly utilized in Section 17.3.1 to cover all lab domains under one certificate.
The entity issuing certificates is called a Certificate Authority (CA). Browsers and operating systems store a trusted root CA list by default within their respective trust stores. Root CAs almost never sign domain certificates directly, delegating that task to an intermediate CA while keeping root keys offline for security. This chain of signatures from domain certificate, to intermediate CA, up to the trusted root CA is known as the chain of trust. Once a browser receives a certificate from a server, it traces this chain until finding a root CA already in its trust store; if the chain is broken or leads to an unknown CA, the browser displays a security warning instead of blindly opening the connection.
A certificate signed by oneself without a third-party CA is called a self-signed certificate. Cryptographically, its encryption strength is identical to a public CA-issued certificate, but because no chain of trust leads to any root CA recognized by browsers, a self-signed certificate always triggers security warnings in Visitor browsers. Such certificates are suitable for internal needs like labs, staging environments, or server-to-server communications fully controlled by Sysadmins, but are never suitable for public services accessed by general Visitors: a point explored further in Section 17.2.3 when attempting to issue certificates for our own lab domains.
17.2 Certbot and Let's Encrypt
Let's Encrypt is a non-profit Certificate Authority operated by the Internet Security Research Group (ISRG) since 2015, issuing free and fully automated TLS certificates. Its presence became one of the primary drivers for massive HTTPS adoption across the public web, as commercial CA TLS certificates were historically paid with manual issuance processes. Certbot, developed by the Electronic Frontier Foundation (EFF), is the most popular official client for communicating with Let's Encrypt and will be used throughout this chapter.
17.2.1 ACME: How Let's Encrypt Verifies Domain Ownership
Let's Encrypt issues certificates via a standardized protocol named ACME (Automated Certificate Management Environment, RFC 8555). Unlike traditional commercial CAs that manually verify applicant identities, ACME proves domain ownership automatically via challenges that clients, such as Certbot, must solve before a certificate is issued. The two most common challenge types used are:
- HTTP-01: Certbot places a file containing a unique token at the path
/.well-known/acme-challenge/on the web server, then Let's Encrypt servers attempt to fetch that file over HTTP on port 80 of the requested domain. If retrieved successfully with matching content, domain ownership is considered proven. - DNS-01: Certbot is prompted to create a specific
TXTrecord on the domain's public DNS zone, then Let's Encrypt servers verify its existence via standard DNS queries. This challenge type is mandatory for wildcard certificates (e.g.,*.example.com) and useful when port 80 cannot be accessed directly from the internet.
A prerequisite applicable to both challenge types often overlooked by beginners: Let's Encrypt validation servers must be able to reach the requested domain over the public internet, either via HTTP in HTTP-01 or via public DNS in DNS-01. This point will be demonstrated in Section 17.2.3.
17.2.2 Certbot Installation
Ubuntu Server provides Certbot directly from official repositories, allowing standard APT installation without adding third-party repositories, consistent with package installation habits established since Chapter 6. Certbot from APT is split into core packages and separate plugin packages for each web server, preventing servers using only Nginx from installing Apache dependencies, and vice versa.
Practical Steps
- Update the package list, then install the core Certbot package.
This package alone suffices to issue certificates viasudo apt update sudo apt install certbotstandaloneorwebrootplugins, but cannot yet be used with--nginxor--apacheflags to be tested in Section 17.2.3. - Install the
python3-certbot-nginxplugin so Certbot can read and edit Nginx configurations automatically via the--nginxflag. Also installpython3-certbot-apacheas our lab has used Apache since Chapter 13, although this package becomes relevant as a reference in Section 17.2.4.sudo apt install python3-certbot-nginx python3-certbot-apache - Verify successful installation by checking its version.
certbot --version
Verification and Troubleshooting
- Displayed version numbers may vary depending on when Ubuntu repositories were last synchronized, so do not be surprised if numbers differ slightly from module examples, similar to the note for PHP versions in Section 14.1.1.
- To confirm both plugins are installed, check via
dpkg.
Output should display three package lines withdpkg -l | grep certbotiistatus in the first column:certbot,python3-certbot-nginx, andpython3-certbot-apache.
17.2.3 Attempting to Issue Certificates for Lab Domains
Let's demonstrate firsthand what happens when attempting to issue a Let's Encrypt certificate for the www.example.local domain built since Chapter 12. Nginx from Chapter 16 should still be active on this server, so we use the newly installed --nginx plugin to edit the Nginx configuration automatically.
Practical Steps
- Run Certbot with the Nginx plugin, targeting both
example.localandwww.example.localdomains simultaneously.sudo certbot --nginx -d example.local -d www.example.local - Certbot will prompt for an email address for certificate expiration notifications, then request agreement to Terms of Service. Fill in as necessary and proceed.
Shortly after, this process will fail with an error message similar to the following:
Certbot failed to authenticate some domains (authenticator: nginx). The Certificate Authority reported these problems:
Domain: www.example.local
Type: dns
Detail: DNS problem: NXDOMAIN looking up A for www.example.local - check that a DNS record exists for this domain
Hint: The Certificate Authority failed to download the challenge files from the temporary standalone webserver started by Certbot on port 80. Ensure that the listed domains point to this machine and that it can accept inbound connections from the internet.This failure is not a bug, but proof that the HTTP-01 mechanism in Section 17.2.1 functions as intended. The example.local domain resolves perfectly inside our lab network thanks to BIND9 from Chapter 9, but the .local TLD is not registered in public root DNS used across the internet. Therefore, Let's Encrypt servers attempting to lookup this domain from outside the lab network will always find it unknown. Not just Certbot or Let's Encrypt, any public CA complying with CA/Browser Forum rules will never issue certificates for domains whose ownership cannot be verified from the public internet. This is a fundamental limitation of public CA-based trust systems, not merely a tooling limitation.
Verification and Troubleshooting
- The same error appears for newly registered real public domains not yet pointed to the server's public IP via
A/AAAArecords, or servers with port 80 blocked by firewalls/NAT from the internet. The messageDNS problem: NXDOMAINindicates the domain itself was not found, whereasFetching ... Connection refusedorConnection timed outindicates the domain resolved but the server could not be reached on port 80. - In the field, mistakenly assuming internal domains like
.local,.internal, or.lancan receive Let's Encrypt certificates is a common trap for new Sysadmins. Sections 17.2.4 and 17.3 address two different solutions for this scenario, depending on whether the domain is genuinely public or purely internal.
17.2.4 Certbot Workflow for Real Public Domains
For Sysadmins managing production servers with registered public domains pointed via public DNS to server IPs (not private IPs like 192.168.1.20 used in this lab), the above Certbot workflow will run smoothly without obstacles. The command is identical to the attempt in Section 17.2.3, simply replacing lab domains with actual public domains.
sudo certbot --nginx -d your-actual-domain.com -d www.your-actual-domain.comFor Apache, use the --apache plugin installed in Section 17.2.2, which works similarly by reading and editing relevant VirtualHost files directly.
sudo certbot --apache -d your-actual-domain.com -d www.your-actual-domain.comBoth plugins automatically add ssl_certificate (Nginx) or SSLCertificateFile (Apache) directives to existing virtual hosts, while offering options to create automatic HTTP to HTTPS redirects: two exact items to be configured manually in Section 17.3 using lab certificates.
Certbot never places certificates directly inside web server configuration directories. Every successfully issued certificate is stored in /etc/letsencrypt/live/<domain-name>/, specifically as four symlinks pointing to the latest revisions inside /etc/letsencrypt/archive/<domain-name>/:
cert.pem: the domain certificate itself.chain.pem: intermediate CA certificate.fullchain.pem: combination ofcert.pemandchain.pem, referenced byssl_certificatein Nginx andSSLCertificateFilein Apache.privkey.pem: private key, referenced byssl_certificate_keyin Nginx andSSLCertificateKeyFilein Apache.
The --nginx and --apache plugins automatically populate both directives with paths pointing to fullchain.pem and privkey.pem inside the live directory rather than the archive directory, eliminating manual path adjustments as done with self-signed certificates in Section 17.3. This detail is also essential for auto-renewal in Section 17.4: whenever Certbot renews a certificate, the archive contents increment with new revisions, and symlinks in live automatically update to point to those revisions. Thus, web server configurations pointing to live paths require no modification post-renewal.
For servers not using Nginx or Apache as a reverse proxy upfront (e.g., mail or database servers needing standalone TLS certificates), Certbot provides the --standalone plugin to run a temporary web server on port 80 to complete challenges, and the --webroot plugin to place challenge files directly into an existing web server's document root without modifying configurations.
| Aspect | Public Domain + Let's Encrypt | Internal Domain/Lab + Self-Signed |
|---|---|---|
| Visitor Browser Trust | Automatically trusted, no warnings | Always triggers security warnings |
| Cost | Free | Free |
| Domain Requirements | Must be public and verifiable from internet | Any, including .local domains |
| Storage Location | /etc/letsencrypt/live/<domain>/ | Customizable, e.g., /etc/ssl/example.local/ |
| Validity Period | 90 days, requires auto-renewal | Custom defined on creation, typically 365 days |
| Use Cases | Public services accessed by general Visitors | Labs, staging, internal server communication |
17.3 TLS Configuration in Nginx and Apache with Self-Signed Certificates
Because our lab domain is purely internal, this section uses self-signed certificates to practice actual TLS configuration mechanisms in Nginx and Apache. Directives used, such as ssl_certificate in Nginx and SSLCertificateFile in Apache, match those automatically added by Certbot for real public domains in Section 17.2.4. The only differences are certificate sources and storage locations, not web server configuration methods.
17.3.1 Creating a Multi-Domain Self-Signed Certificate with OpenSSL
Thanks to the SAN extension discussed in Section 17.1.2, a single certificate can cover all lab domains simultaneously (example.local, www.example.local, blog.example.local, and app.example.local), avoiding repetitive processes for each virtual host. Unlike Let's Encrypt certificates fixed at /etc/letsencrypt/live/, self-signed certificates can be stored anywhere; we use /etc/ssl/example.local/ to distinguish them from system default certificates in /etc/ssl/certs/.
Practical Steps
- Create a dedicated directory to store lab certificates and private keys.
sudo mkdir -p /etc/ssl/example.local - Create an OpenSSL configuration file defining certificate identity and SAN entries.
sudo nano /etc/ssl/example.local/san.cnf[req] distinguished_name = req_distinguished_name x509_extensions = v3_req prompt = no [req_distinguished_name] C = ID ST = DKI Jakarta L = Jakarta O = Linux Server 101 Lab CN = example.local [v3_req] subjectAltName = @alt_names [alt_names] DNS.1 = example.local DNS.2 = www.example.local DNS.3 = blog.example.local DNS.4 = app.example.local - Generate private key and self-signed certificate simultaneously in one command, valid for 365 days.
Thesudo openssl req -x509 -nodes -newkey rsa:2048 \ -keyout /etc/ssl/example.local/example.local.key \ -out /etc/ssl/example.local/example.local.crt \ -days 365 \ -config /etc/ssl/example.local/san.cnf-nodesflag stores the private key without a passphrase: a standard choice for web server certificates, as passphrases prevent Nginx or Apache from starting automatically without manual intervention upon service restarts. - Restrict private key permissions to root read-only, following standard security practices for cryptographic key files.
sudo chmod 600 /etc/ssl/example.local/example.local.key
Verification and Troubleshooting
- Inspect the newly created certificate, ensuring all four domains are listed in the SAN extension.
Output must show all four domains matchingopenssl x509 -in /etc/ssl/example.local/example.local.crt -noout -text | grep -A1 "Subject Alternative Name"san.cnfentries. - If
openssl reqfails with errors regarding missingalt_names, recheck indentation and bracketed section headers insan.cnf, as OpenSSL strictly parses configuration syntax.
17.3.2 Enabling HTTPS in Nginx for All Virtual Hosts
With certificates ready, the next step adds a new server block listening on port 443 for each Nginx virtual host built, while redirecting all HTTP traffic on port 80 to HTTPS so Visitors cannot access unencrypted versions.
Practical Steps
- Allow port 443 in the firewall, as Chapter 12 only opened port 80.
sudo ufw allow 443/tcp - Edit the
example.localvirtual host configuration, converting existingserverblocks to HTTPS blocks and adding a new block for HTTP redirects.sudo nano /etc/nginx/sites-available/example.local
Theserver { listen 443 ssl; listen [::]:443 ssl; server_name example.local www.example.local; root /var/www/example.local/html; ssl_certificate /etc/ssl/example.local/example.local.crt; ssl_certificate_key /etc/ssl/example.local/example.local.key; ssl_protocols TLSv1.2 TLSv1.3; location / { try_files $uri $uri/ =404; } error_page 404 /404.html; location = /404.html { internal; } } server { listen 80; listen [::]:80; server_name example.local www.example.local; return 301 https://$host$request_uri; }ssl_protocolsdirective restricts Nginx to accept TLS 1.2 and TLS 1.3 only, rejecting older insecure versions like TLS 1.0 and 1.1. The second block serves no content, tasked solely with forcing HTTPS redirects via301 Moved Permanentlystatus codes. - Repeat the pattern for
blog.example.local.sudo nano /etc/nginx/sites-available/blog.example.localserver { listen 443 ssl; listen [::]:443 ssl; server_name blog.example.local; root /var/www/blog.example.local/html; ssl_certificate /etc/ssl/example.local/example.local.crt; ssl_certificate_key /etc/ssl/example.local/example.local.key; ssl_protocols TLSv1.2 TLSv1.3; location / { try_files $uri $uri/ =404; } } server { listen 80; listen [::]:80; server_name blog.example.local; return 301 https://$host$request_uri; } - Apply the pattern to
app.example.localfrom Chapter 16: addssl_certificate,ssl_certificate_key, andssl_protocolsdirectives to the existingserverblock, updatelistento443 ssl, and add the port 80 redirect block. Theupstreamblock andproxy_passdirective remain unchanged. - Test overall configuration syntax, then reload.
sudo nginx -t sudo systemctl reload nginx - Test from another machine on the same network. The
-kflag allows curl to proceed despite self-signed certificates.curl -k https://www.example.local curl -k https://blog.example.local curl -k https://app.example.local - Ensure HTTP to HTTPS redirection works.
The response must display statuscurl -I http://www.example.local301with aLocationheader pointing to thehttps://version of the domain.
Verification and Troubleshooting
- Without the
-kflag, curl rejects connections withcurl: (60) SSL certificate problem: self-signed certificate. This indicates proper curl behavior when encountering unverified issuer CAs, matching warnings displayed in Visitor browsers without manual exceptions. - If
nginx: [emerg] cannot load certificateoccurs duringnginx -t, verify file paths inssl_certificateand ensure Nginx master processes running as root can access/etc/ssl/example.local/. - To view certificate details sent during handshakes, run
openssl s_clientdirectly from the terminal:
This command displays the subject andecho | openssl s_client -connect www.example.local:443 -servername www.example.local 2>/dev/null | openssl x509 -noout -dates -subjectnotBefore/notAfterdates, verifying that the server serves the newly generated certificate.
17.3.3 Enabling HTTPS in Apache and Redirecting HTTP to HTTPS
Apache virtual hosts from Chapter 13 for example.local and blog.example.local remain untouched until now. This section adds HTTPS to both, reusing the SAN certificate generated in Section 17.3.1.
Practical Steps
- Stop Nginx and start Apache, following the port 80 toggle routine established since Chapters 13 and 14.
sudo systemctl stop nginx sudo systemctl start apache2 - Enable the
sslmodule, which providesSSL*directives required for Apache to serve HTTPS.sudo a2enmod ssl - Edit
example.local.conf, adding a port 443VirtualHostblock below the existing block, then converting the old block into a redirect.sudo nano /etc/apache2/sites-available/example.local.conf
The<VirtualHost *:80> ServerName example.local ServerAlias www.example.local Redirect permanent / https://example.local/ </VirtualHost> <VirtualHost *:443> ServerName example.local ServerAlias www.example.local DocumentRoot /var/www/example.local/html SSLEngine on SSLCertificateFile /etc/ssl/example.local/example.local.crt SSLCertificateKeyFile /etc/ssl/example.local/example.local.key SSLProtocol -all +TLSv1.2 +TLSv1.3 <Directory /var/www/example.local/html> Options -Indexes AllowOverride None Require all granted </Directory> ErrorLog ${APACHE_LOG_DIR}/example.local.error.log CustomLog ${APACHE_LOG_DIR}/example.local.access.log combined </VirtualHost>SSLProtocol -all +TLSv1.2 +TLSv1.3directive mirrors Nginx'sssl_protocols TLSv1.2 TLSv1.3;, disabling older protocol versions before enabling secure ones. - Repeat for
blog.example.local.conf.sudo nano /etc/apache2/sites-available/blog.example.local.conf<VirtualHost *:80> ServerName blog.example.local Redirect permanent / https://blog.example.local/ </VirtualHost> <VirtualHost *:443> ServerName blog.example.local DocumentRoot /var/www/blog.example.local/html SSLEngine on SSLCertificateFile /etc/ssl/example.local/example.local.crt SSLCertificateKeyFile /etc/ssl/example.local/example.local.key SSLProtocol -all +TLSv1.2 +TLSv1.3 <Directory /var/www/blog.example.local/html> Options -Indexes AllowOverride None Require all granted </Directory> ErrorLog ${APACHE_LOG_DIR}/blog.example.local.error.log CustomLog ${APACHE_LOG_DIR}/blog.example.local.access.log combined </VirtualHost> - Test configuration syntax, then restart Apache. Module changes require full service restarts rather than simple reloads, following practices from Section 13.3.1.
sudo apache2ctl configtest sudo systemctl restart apache2 - Test connectivity from another network machine.
curl -k https://example.local curl -I http://example.local - After Apache testing completes, restore Nginx as the active web server to align with Chapter 14 states and Chapter 17 practices.
sudo systemctl stop apache2 sudo systemctl start nginx
Verification and Troubleshooting
- The
Apache FullUFW profile enabled in Chapter 13 includes port 443, eliminating additional firewall rule setup for Apache in this section. - If
apache2ctl configtestreportsSSL Library Errorconcerning certificate files, recheck directory permissions on/etc/ssl/example.local/. Unlike BIND9 in Chapter 9, Apache on Ubuntu does not run under strict AppArmor profile directory restrictions, making standard Linux permission errors the primary cause. - The
Redirect permanentdirective frommod_aliasis enabled by default in Apache without additionala2enmodcommands, unlikemod_rewriteredirects which require separate module activation for advanced routing needs.
17.4 Certificate Auto-renewal
Let's Encrypt certificates carry a short 90-day validity lifetime by design, encouraging ecosystem automation rather than error-prone manual renewals. This section covers automatic renewal operations for Let's Encrypt certificates alongside expiration monitoring methods for self-signed lab certificates.
17.4.1 Let's Encrypt Auto-renewal via Certbot
The certbot APT package installed in Section 17.2.2 includes periodic renewal jobs enabled by default post-installation without manual Sysadmin configuration. Scheduled via systemd timer mechanisms detailed in Chapter 5, the job runs twice daily to check all Certbot-issued certificates on the server. Certbot only renews certificates with 30 days or less remaining validity, preventing unnecessary server loads on Let's Encrypt infrastructure.
Practical Steps
- Inspect active renewal timers.
Output displays the next execution time forsystemctl list-timers | grep certbotcertbot.timer, confirming background renewal processing.
For servers operating active Let's Encrypt certificates issued in Section 17.2.4, the following commands manage manual renewal operations:
sudo certbot renew --dry-run
sudo certbot certificatesThe --dry-run flag simulates the renewal process (including ACME challenges) without replacing active certificates: a safe method to test automated execution prior to certificate expiration. Running certbot certificates lists managed certificates, their validity periods, and file paths to fullchain.pem and privkey.pem inside /etc/letsencrypt/live/.
Verification and Troubleshooting
- Since lab domain verification failed in Section 17.2.3, running
certbot renew --dry-runandcertbot certificateson this test server will report no managed certificates, which is expected behavior. Output appears only on servers carrying valid Let's Encrypt certificates from Section 17.2.4. - If
certbot.timeris missing fromsystemctl list-timers, check if the service unit is active and enabled:systemctl status certbot.timer
17.4.2 Expiration Monitoring for Self-Signed Certificates
Self-signed certificates created in Section 17.3.1 are not managed by Certbot, meaning automatic renewal services do not process them. Tracking expiration remains a manual Sysadmin responsibility.
Practical Steps
- Check expiration dates directly from certificate files.
Output displays a singleopenssl x509 -enddate -noout -in /etc/ssl/example.local/example.local.crtnotAfter=line indicating the expiration date, set 365 days from creation in Section 17.3.1. - To avoid missing expiration windows, set calendar reminders or write check scripts executed via systemd timers (as covered in Chapter 5):
Theopenssl x509 -checkend 2592000 -noout -in /etc/ssl/example.local/example.local.crt && echo "Certificate valid for more than 30 days" || echo "Certificate expires within 30 days"-checkendflag accepts seconds (2592000equals 30 days), matching the threshold used by Certbot internally in Section 17.4.1.
Verification and Troubleshooting
- When lab certificates approach expiration, re-run the
openssl reqcommand from Section 17.3.1 using the existingsan.cnffile to generate updated certificates, then reload Nginx and Apache to read new files from disk. - For environments beyond personal labs (such as multiple internal servers requiring TLS certificates), individual self-signed certificate generation becomes cumbersome and prone to human error. A robust alternative involves running an internal private CA (e.g., via tools like
step-ca). Distributing one internal root certificate across office devices allows internal servers to request certificates directly from that CA: an advanced topic beyond this series scope.
At this point, all virtual hosts built since Chapter 12 (example.local, www.example.local, blog.example.local in Nginx/Apache, and app.example.local from Chapter 16) serve HTTPS complete with automatic HTTP redirects. Core differences between Let's Encrypt public domain certificates and self-signed internal certificates have been addressed, including storage path distinctions. Part IV of the web services series concludes here. Chapter 18 opens Part V, transitioning to database servers using PostgreSQL 18: components running behind web applications served via Nginx and Apache throughout this section.

