Web Server with Apache

Web Server with Apache

Bitnesia Aug 28, 2026 2 ID

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

  1. Temporarily stop the Nginx service.
    sudo systemctl stop nginx
  2. Update the package list, then install Apache.
    sudo apt update
    sudo apt install apache2
  3. Just like the nginx package 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
  4. The apache2 package registers application profiles with UFW automatically, unlike Nginx where we previously opened ports manually. Check the available profiles.
    sudo ufw app list
    Three Apache-related profiles will appear: Apache (port 80 only), Apache Secure (port 443 only), and Apache Full (ports 80 and 443 simultaneously). Because we will also enable mod_ssl in Section 13.3.2 later, allow the Apache Full profile directly so there is no need to modify UFW rules again later.
    sudo ufw allow 'Apache Full'
  5. Test from another computer on the same network.
    curl http://192.168.1.20
    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.

Verification and Troubleshooting

  • Check the exact version of Apache installed.
    apache2 -v
    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.
  • If systemctl status shows 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:
    sudo ss -tlnp | grep :80
    If Nginx is still seen listening on that port, repeat sudo systemctl stop nginx then run sudo 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

  1. Inspect the contents of the main configuration file.
    cat /etc/apache2/apache2.conf
    The most relevant part of this file is the following Include lines, which determine which additional configurations Apache reads.
    IncludeOptional mods-enabled/*.load
    IncludeOptional mods-enabled/*.conf
    IncludeOptional conf-enabled/*.conf
    IncludeOptional sites-enabled/*.conf
  2. Inspect the contents of ports.conf, a separate file that configures which ports Apache listens on.
    cat /etc/apache2/ports.conf
    The line Listen 80 is present there by default. The line Listen 443 for HTTPS only appears later, wrapped inside the condition <IfModule mod_ssl.c>, which means that port is only truly listened to after we activate mod_ssl in Section 13.3.2.
  3. Compare the following directories with the familiar sites-available/sites-enabled pair from Nginx.
    ls /etc/apache2/
    Besides sites-available and sites-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 -available directory are only active if there is a symlink to the -enabled directory.
  4. Unlike Nginx, Apache provides dedicated commands to manage these symlinks, so we do not need to use ln -s manually. 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 -t habit we built in Chapter 12, make it a habit to test the Apache configuration before applying it.
    sudo apache2ctl configtest
    A healthy output shows Syntax OK.
  • The command apache2ctl configtest often outputs the warning AH00558: apache2: Could not reliably determine the server's fully qualified domain name even if the configuration is actually valid. This warning appears because the ServerName directive has not been set globally in apache2.conf, and it is safe to ignore for lab purposes, but on production servers it is best to add a ServerName line in /etc/apache2/conf-available/servername.conf to 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

  1. Create a new virtual host file in sites-available. The .conf extension must be used, because a2ensite searches for it based on this file name pattern.
    sudo nano /etc/apache2/sites-available/example.local.conf
  2. Fill it with the following configuration.
    <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>
    The <Directory> block sets access permissions for that filesystem path. Require all granted is the access control syntax for Apache 2.4 and above, replacing the combination of Order allow,deny and Allow from all used in older versions, while AllowOverride All permits .htaccess files inside this directory to override parts of the configuration, a feature we will utilize in Section 13.3.1.
  3. Enable this virtual host with a2ensite, the replacement for the manual ln -s we used in Nginx.
    sudo a2ensite example.local.conf
  4. Disable the 000-default site so it no longer acts as a confusing fallback.
    sudo a2dissite 000-default.conf
  5. Test the syntax, then apply the changes.
    sudo apache2ctl configtest
    sudo systemctl reload apache2
  6. Test from another computer on the same network.
    curl http://www.example.local
    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.

Verification and Troubleshooting

  • A 403 Forbidden error on Apache 2.4 and above almost always means the Require all granted line 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.conf was executed and systemctl reload apache2 was 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

  1. 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
  2. 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>
  3. Enable, test, and apply.
    sudo a2ensite blog.example.local.conf
    sudo apache2ctl configtest
    sudo systemctl reload apache2
  4. 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 Host header, just like the name-based virtual host mechanism in Nginx.
  • Just like Nginx, if two virtual hosts accidentally share identical ServerName directives, Apache will not display an error during configtest. 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

  1. Enable the rewrite module.
    sudo a2enmod rewrite
    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 systemctl restart apache2
  2. Add the rewrite rule into the <Directory> block in the example.local virtual host.
    sudo nano /etc/apache2/sites-available/example.local.conf
    <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>
    The R=301 flag causes Apache to send a permanent redirect to the browser rather than serving new content silently, while the L (last) flag tells Apache to stop processing other rewrite rules once this line matches.
  3. Apply and test.
    sudo apache2ctl configtest
    sudo systemctl reload apache2
    curl -I http://www.example.local/halaman-lama
    The response header should show HTTP/1.1 301 Moved Permanently along with the line Location: /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 .htaccess file inside the document root instead of the virtual host file, which is why AllowOverride All in Section 13.2.1 was intentionally enabled. The difference is that Apache re-reads the .htaccess file 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 .htaccess for 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

  1. Enable the ssl module.
    sudo a2enmod ssl
  2. Enable the default-ssl site provided by the apache2 package, complete with the self-signed certificate from the ssl-cert package which is generally pre-installed on Ubuntu Server.
    sudo a2ensite default-ssl.conf
  3. Restart Apache, because activating the ssl module requires Apache to open a new listener on port 443.
    sudo systemctl restart apache2
  4. Test from another computer on the same network. Add the -k flag 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 -k flag 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-ssl site serves all domains by default with the document root /var/www/html, separate from the custom example.local virtual 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.

AspectApacheNginx
Connection handling modelMPM (prefork, worker, event)Event-driven from the ground up
Per-directory configurationFully supported via .htaccessNo direct equivalent, all in central configuration
Module managementDynamic via a2enmod/a2dismod, no recompilation requiredMost built-in modules must be compiled during build; dynamic modules supported since v1.9.11 but less commonly used
Core strengthConfiguration flexibility, legacy application compatibilityResource efficiency, reliable as a reverse proxy and load balancer
Common use caseHosting legacy CMS/PHP apps, shared hostingModern 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 nginx

Retest to ensure Nginx is back to responding on the same domain.

curl http://www.example.local

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

```