Nginx that we installed in Chapter 12 is already serving the example.local and blog.example.local domains well, but in the field Sysadmins rarely have the luxury of choosing a single web server for the entire infrastructure. Many legacy applications, especially CMSs like WordPress or PHP frameworks built about a decade ago, were designed under the assumption of running on top of Apache. The .htaccess file structure that serves as the backbone for these applications is a signature Apache feature that has no direct equivalent in Nginx. Sooner or later, we will encounter production servers that require Apache to remain installed, whether due to legacy infrastructure heritage or because application vendors specifically require it.
This chapter puts Apache HTTP Server into practice, covering installation, configuration structure, and virtual hosts, following the same flow as Chapter 12. After that, we discuss two important modules, mod_rewrite and mod_ssl, then conclude the chapter with a direct comparison of Apache vs Nginx so that we have an objective foundation for choosing one based on project requirements.
13.1 Installation and Apache Configuration Structure
Apache HTTP Server, often simply abbreviated as "Apache" or also known by the process name httpd in many non-Debian distributions, is a web server that has existed since 1995 and dominated the global web server market share for years before Nginx gained popularity in the mid-2000s. Unlike the event-driven architecture of Nginx that we discussed in Chapter 12, Apache traditionally uses a Multi-Processing Module (MPM) model that determines how each connection is handled, ranging from prefork which creates a separate process per connection, worker which utilizes threads inside processes, to event which actually approaches the event-driven approach of Nginx. Ubuntu Server uses the event MPM as default since recent releases, so the performance gap between Apache and Nginx for general workloads is actually not as wide as many people assume, a point we will discuss in greater detail in Section 13.4.
13.1.1 Installing Apache from the Ubuntu Repository
Nginx from Chapter 12 is likely still running and listening on port 80 on the same server. Because two web servers cannot listen on the same port without custom configuration, stop Nginx first before installing Apache so that we can practice Apache without port conflict interruptions. We will reactivate Nginx at the end of this chapter.
Hands-on Steps
- Temporarily stop the Nginx service.
sudo systemctl stop nginx - Update the package list, then install Apache.
sudo apt update sudo apt install apache2 - Just like the
nginxpackage in Chapter 12, the Debian/Ubuntu package for Apache automatically enables and starts its service as soon as the installation finishes. Confirm its status.systemctl status apache2.service - The
apache2package registers application profiles with UFW automatically, unlike Nginx where we previously opened ports manually. Check the available profiles.
Three Apache-related profiles will appear:sudo ufw app listApache(port 80 only),Apache Secure(port 443 only), andApache Full(ports 80 and 443 simultaneously). Because we will also enablemod_sslin Section 13.3.2 later, allow theApache Fullprofile directly so there is no need to modify UFW rules again later.sudo ufw allow 'Apache Full' - Test from another computer on the same network.
If successful, the response will be a default HTML page titled "Apache2 Ubuntu Default Page", a sign that Apache is already serving requests with its default configuration.curl http://192.168.1.20
Verification and Troubleshooting
- Check the exact version of Apache installed.
The exact version may vary depending on when the Ubuntu repository was last synchronized, so do not be surprised if the number does not match the example in this module exactly.apache2 -v - If
systemctl statusshows that the service failed to start with a message like(98)Address already in use: AH00072: make_sock: could not bind to address 0.0.0.0:80, Nginx most likely has not completely stopped. Check with:
If Nginx is still seen listening on that port, repeatsudo ss -tlnp | grep :80sudo systemctl stop nginxthen runsudo systemctl start apache2. - If curl from another computer still fails even though the service is
active (running), recheck the UFW rules.sudo ufw status
13.1.2 Anatomy of Apache Configuration Directory
The Apache configuration structure in Ubuntu shares a similar philosophy with Nginx, separating core configuration from per-site and per-module configurations, but Apache goes a step further by providing dedicated helper commands to manage everything, rather than relying on manual symbolic links as we did in Nginx.
Hands-on Steps
- Inspect the contents of the main configuration file.
The most relevant part of this file is the followingcat /etc/apache2/apache2.confIncludelines, which determine which additional configurations Apache reads.IncludeOptional mods-enabled/*.load IncludeOptional mods-enabled/*.conf IncludeOptional conf-enabled/*.conf IncludeOptional sites-enabled/*.conf - Inspect the contents of
ports.conf, a separate file that configures which ports Apache listens on.
The linecat /etc/apache2/ports.confListen 80is present there by default. The lineListen 443for HTTPS only appears later, wrapped inside the condition<IfModule mod_ssl.c>, which means that port is only truly listened to after we activatemod_sslin Section 13.3.2. - Compare the following directories with the familiar
sites-available/sites-enabledpair from Nginx.
Besidesls /etc/apache2/sites-availableandsites-enabled, Apache has similar pairs for modules (mods-available/mods-enabled) and for other additional configurations (conf-available/conf-enabled). All three use the exact same pattern: files in the-availabledirectory are only active if there is a symlink to the-enableddirectory. - Unlike Nginx, Apache provides dedicated commands to manage these symlinks, so we do not need to use
ln -smanually. View the currently active sites.sudo apache2ctl -S
The commands a2ensite and a2dissite enable and disable sites respectively, a2enmod and a2dismod do the same for modules, while a2enconf and a2disconf are for additional configurations. We will use all of them throughout this chapter. The default site named 000-default is active from the start, which is what answered our curl earlier with the "Apache2 Ubuntu Default Page" page, and its content is served from /var/www/html, the exact same document root as the default Nginx in Chapter 12.
Verification and Troubleshooting
- Just like the
nginx -thabit we built in Chapter 12, make it a habit to test the Apache configuration before applying it.
A healthy output showssudo apache2ctl configtestSyntax OK. - The command
apache2ctl configtestoften outputs the warningAH00558: apache2: Could not reliably determine the server's fully qualified domain nameeven if the configuration is actually valid. This warning appears because theServerNamedirective has not been set globally inapache2.conf, and it is safe to ignore for lab purposes, but on production servers it is best to add aServerNameline in/etc/apache2/conf-available/servername.confto remove it permanently.
13.2 Virtual Hosts in Apache
The concept called a server block in Nginx is called a virtual host in Apache, written as a <VirtualHost> block inside the configuration file. Just like Nginx, a single physical server or IP address can serve multiple domains simultaneously through this mechanism, and Apache also relies on the Host header from HTTP requests to determine which virtual host should answer, via the ServerName and ServerAlias directives whose function is equivalent to server_name in Nginx.
13.2.1 Creating a Virtual Host for example.local
The document root /var/www/example.local/html that we prepared in Chapter 12 still contains index.html and 404.html, and its ownership is already www-data:www-data, the same user used by Apache by default. We can reuse this directory directly, a practice that also proves that domains and static content are not tied to a specific web server, only the underlying configuration differs.
Hands-on Steps
- Create a new virtual host file in
sites-available. The.confextension must be used, becausea2ensitesearches for it based on this file name pattern.sudo nano /etc/apache2/sites-available/example.local.conf - Fill it with the following configuration.
The<VirtualHost *:80> ServerName example.local ServerAlias www.example.local DocumentRoot /var/www/example.local/html <Directory /var/www/example.local/html> Options -Indexes +FollowSymLinks AllowOverride All Require all granted </Directory> ErrorLog ${APACHE_LOG_DIR}/example.local.error.log CustomLog ${APACHE_LOG_DIR}/example.local.access.log combined </VirtualHost><Directory>block sets access permissions for that filesystem path.Require all grantedis the access control syntax for Apache 2.4 and above, replacing the combination ofOrder allow,denyandAllow from allused in older versions, whileAllowOverride Allpermits.htaccessfiles inside this directory to override parts of the configuration, a feature we will utilize in Section 13.3.1. - Enable this virtual host with
a2ensite, the replacement for the manualln -swe used in Nginx.sudo a2ensite example.local.conf - Disable the
000-defaultsite so it no longer acts as a confusing fallback.sudo a2dissite 000-default.conf - Test the syntax, then apply the changes.
sudo apache2ctl configtest sudo systemctl reload apache2 - Test from another computer on the same network.
The response that appears should be identical to the page previously served by Nginx in Chapter 12, because both web servers read the exact same HTML file from disk.curl http://www.example.local
Verification and Troubleshooting
- A
403 Forbiddenerror on Apache 2.4 and above almost always means theRequire all grantedline is missing or misspelled in the<Directory>block. This is a common pitfall for Sysadmins used to older Apache configurations. - If curl still returns the Apache default page instead of the example.local page, make sure
a2dissite 000-default.confwas executed andsystemctl reload apache2was executed afterward. - Verify that the active virtual host matches expectations.
sudo apache2ctl -S
13.2.2 Multiple Domain Name-Based Virtual Hosts
The domain blog.example.local already has a DNS record from Chapter 12. This section adds a second virtual host for that domain in Apache, following the same name-based virtual hosting pattern.
Hands-on Steps
- Prepare a dedicated document root for this second virtual host.
sudo mkdir -p /var/www/blog.example.local/html echo "<h1>Blog example.local on Apache</h1>" | sudo tee /var/www/blog.example.local/html/index.html sudo chown -R www-data:www-data /var/www/blog.example.local - Create a new virtual host.
sudo nano /etc/apache2/sites-available/blog.example.local.conf<VirtualHost *:80> ServerName blog.example.local DocumentRoot /var/www/blog.example.local/html <Directory /var/www/blog.example.local/html> Options -Indexes +FollowSymLinks AllowOverride All Require all granted </Directory> ErrorLog ${APACHE_LOG_DIR}/blog.example.local.error.log CustomLog ${APACHE_LOG_DIR}/blog.example.local.access.log combined </VirtualHost> - Enable, test, and apply.
sudo a2ensite blog.example.local.conf sudo apache2ctl configtest sudo systemctl reload apache2 - Test both domains from the same server IP.
curl http://www.example.local curl http://blog.example.local
Verification and Troubleshooting
- Compare the content of the two domains. The content difference proves Apache is actually selecting the virtual host based on the
Hostheader, just like the name-based virtual host mechanism in Nginx. - Just like Nginx, if two virtual hosts accidentally share identical
ServerNamedirectives, Apache will not display an error duringconfigtest. The virtual host defined earlier in the file reading order will be used, a classic source of confusion that does not trigger any error message.
13.3 Essential Modules: mod_rewrite and mod_ssl
Apache's strength relies heavily on its module architecture, which can be enabled or disabled on the fly without recompiling the binary, using the a2enmod command touched upon in Section 13.1.2. Two modules most commonly used by Sysadmins in the field are mod_rewrite for URL rewriting and mod_ssl for enabling HTTPS.
13.3.1 mod_rewrite for URL Rewriting
mod_rewrite allows Apache to modify or redirect URLs based on specific patterns, using regular expression syntax. This module is the backbone of many legacy PHP applications and CMSs like WordPress for creating SEO-friendly URLs, or simply redirecting visitors from old URLs to new ones after a website structure changes.
Hands-on Steps
- Enable the
rewritemodule.
Unlike standard site changes that only require a reload, enabling a new module requires a full restart of Apache, because modules are loaded once when the Apache process starts initially, rather than re-read upon configuration reloads.sudo a2enmod rewritesudo systemctl restart apache2 - Add the rewrite rule into the
<Directory>block in theexample.localvirtual host.sudo nano /etc/apache2/sites-available/example.local.conf
The<Directory /var/www/example.local/html> Options -Indexes +FollowSymLinks AllowOverride All Require all granted RewriteEngine On RewriteRule ^halaman-lama$ /index.html [R=301,L] </Directory>R=301flag causes Apache to send a permanent redirect to the browser rather than serving new content silently, while theL(last) flag tells Apache to stop processing other rewrite rules once this line matches. - Apply and test.
The response header should showsudo apache2ctl configtest sudo systemctl reload apache2 curl -I http://www.example.local/halaman-lamaHTTP/1.1 301 Moved Permanentlyalong with the lineLocation: /index.html.
Verification and Troubleshooting
- If rewriting has no effect at all and Apache keeps returning
404, make sure the module is actually enabled.apache2ctl -M | grep rewrite - Rewrite rules can also be placed in a
.htaccessfile inside the document root instead of the virtual host file, which is whyAllowOverride Allin Section 13.2.1 was intentionally enabled. The difference is that Apache re-reads the.htaccessfile on every incoming request to that directory, slightly impacting performance compared to rules placed directly in the virtual host file, which are only read once during Apache start or reload. In practice, place rewrite rules directly in the virtual host if you manage the server fully, and reserve.htaccessfor shared hosting cases or when developers need to change rules without root access to the Apache configuration.
13.3.2 mod_ssl for Basic HTTPS
mod_ssl is the module that adds TLS/SSL support to Apache, allowing the web server to serve requests via HTTPS on port 443. This section only practices how to enable the module with the built-in self-signed certificate, simply to ensure the mechanism works. Issuing official and free certificates from Let's Encrypt, complete with auto-renewal, is the main topic of Chapter 17.
Hands-on Steps
- Enable the
sslmodule.sudo a2enmod ssl - Enable the
default-sslsite provided by theapache2package, complete with the self-signed certificate from thessl-certpackage which is generally pre-installed on Ubuntu Server.sudo a2ensite default-ssl.conf - Restart Apache, because activating the
sslmodule requires Apache to open a new listener on port 443.sudo systemctl restart apache2 - Test from another computer on the same network. Add the
-kflag so curl does not reject the connection simply because the certificate is self-signed and not yet trusted by any certificate authority.curl -k https://192.168.1.20
Verification and Troubleshooting
- Ensure port 443 is actively being listened to.
sudo ss -tlnp | grep :443 - The
-kflag in curl should only be used for lab testing like this. Never make a habit of using it to verify production endpoints, because that flag disables certificate validation that is supposed to protect visitors from man-in-the-middle attacks. - The
default-sslsite serves all domains by default with the document root/var/www/html, separate from the customexample.localvirtual host we created ourselves. Connecting SSL certificates to custom domain virtual hosts in the proper way will be fully practiced in Chapter 17.
13.4 Apache vs Nginx: When to Choose Which
After practicing Apache directly, we now have sufficient experience to compare it objectively with Nginx from Chapter 12, not merely based on reputation or forum trends. The Apache vs Nginx debate is often answered too simplistically with "Nginx is faster", even though the actual difference is more complex and heavily dependent on the Sysadmin's use case.
The most fundamental difference lies in the connection handling architecture. Apache historically used the prefork MPM which creates a separate process for each connection, a memory-heavy model when facing thousands of concurrent connections because each process carries its own overhead. Nginx was designed from the beginning with an event-driven model where a single worker process can handle thousands of connections simultaneously in a non-blocking manner, following the C10K problem explanation in Chapter 12. Ubuntu Server now uses the event MPM as default for Apache, a model much closer to Nginx's approach than legacy prefork, so the pure performance gap for static content has actually narrowed significantly compared to comparisons from a decade ago.
What keeps Apache relevant is not raw speed, but per-directory configuration flexibility via .htaccess, a feature we practiced in Section 13.3.1 that has no direct equivalent in Nginx. Many CMS applications and shared hosting environments are built on this assumption, so full migration to Nginx often requires rewriting all rewrite rules into Nginx location syntax, work that is not always worth the benefit for stable legacy applications.
In the field, Sysadmins rarely have to choose one exclusively. A common hybrid pattern is placing Nginx in front as a reverse proxy and static file server facing visitors directly, then forwarding specific requests to Apache behind it for applications relying on mod_php or .htaccess. This reverse proxy pattern mechanism will be deepened in Chapter 16, although the backend example there will use a simple application rather than Apache specifically.
| Aspect | Apache | Nginx |
|---|---|---|
| Connection handling model | MPM (prefork, worker, event) | Event-driven from the ground up |
| Per-directory configuration | Fully supported via .htaccess | No direct equivalent, all in central configuration |
| Module management | Dynamic via a2enmod/a2dismod, no recompilation required | Most built-in modules must be compiled during build; dynamic modules supported since v1.9.11 but less commonly used |
| Core strength | Configuration flexibility, legacy application compatibility | Resource efficiency, reliable as a reverse proxy and load balancer |
| Common use case | Hosting legacy CMS/PHP apps, shared hosting | Modern web server, reverse proxy, high-volume static content |
After this chapter, Chapter 14 will cover PHP and PHP-FPM as a layer to run dynamic applications on top of both Nginx and Apache, before we use Nginx more extensively as the foundation for advanced topics such as reverse proxies and load balancing in Chapter 16, and TLS/SSL with Let's Encrypt in Chapter 17. Therefore, return the server to its initial state so that the Chapter 12 configuration remains consistent for subsequent chapters.
sudo systemctl stop apache2
sudo systemctl disable apache2
sudo systemctl start nginxRetest to ensure Nginx is back to responding on the same domain.
curl http://www.example.localThe Apache installation remains on the server, only its service is inactive and does not start automatically upon boot, so at any time we can reactivate it with sudo systemctl enable --now apache2 without needing a fresh reinstallation.
```

