How to migrate Apache to Nginx by converting VirtualHosts to Server Blocks

In this tutorial we will talk about how to migrate Apache to Nginx. Apache and Nginx are probably the most used Web servers on Linux. The former is the most ancient of the two: its development started in 1995, and it played a very important role in the World Wide Web expansion; it is still the most popular web server around. The first version of Nginx, instead, was released in 2004. Nginx is not only a web server: it can also work as a reverse proxy and a load balancer.

Both Apache and Nginx are free and open source. One of their most important functionalities is the ability to serve multiple websites/resources. Apache uses the so called “VirtualHosts” while Nginx uses “Server Blocks”. In this tutorial we see how to migrate the most common Apache VirtualHost configurations to Nginx.

In this tutorial you will learn:

  • How to install Nginx in Debian and Red Hat based distributions
  • How to migrate Apache to Nginx
  • How to translate Apache VirtualHost configurations to Nginx server blocks
How to migrate Apache to Nginx
How to migrate Apache to Nginx

Software requirements and conventions used

Software Requirements and Linux Command Line Conventions
Category Requirements, Conventions or Software Version Used
System Debian or Red Hat based distributions
Software Nginx
Other Root privileges
Conventions # – requires given linux-commands to be executed with root privileges either directly as a root user or by use of sudo command
$ – requires given linux-commands to be executed as a regular non-privileged user

Nginx installation

Nginx is available in the default repositories of all the most commonly used Linux distributions. Let’s see how to install it on Debian and Red Hat based distributions, using the respective package managers.

On Debian and its large family of derivatives we can choose to use one between the aptitude and apt package managers; here we will use the latter. To install the Nginx we run:

$ sudo apt-get update && sudo apt-get install nginx

In the Red Hat family of distributions, which includes RHEL (Red Hat Enterprise Linux) and Fedora, we can install the software using dnf. The command we should run to install the dedicated package is:

$ sudo dnf install nginx

With the software installed on our system, we can start the nginx service and set it to be automatically launched at boot using the following command:

$ sudo systemctl enable --now nginx

The server listens on port 80 by default, so to verify that it is reachable we can simply navigate to localhost with our favorite web browser. Here is the Nginx welcome page on Fedora:

Nginx welcome page on Fedora
Nginx welcome page on Fedora


Migrate Apache to Nginx – Apache VirtualHosts vs Nginx server blocks

How we said in the introduction of this tutorial, both Apache and Nginx have the ability to serve multiple websites. On Apache the various sites to be served are configured using VirtualHosts; on Nginx Server Blocks are used, instead. Let’s see the most basic Apache VirtualHost directives and how we can translate them to nginx accepted instructions. The VirtualHost below contains very few directives:

<VirtualHost *:80>
    ServerName   site1.lan
    DocumentRoot /var/www/site1.lan
</VirtualHost>

With the very few instructions above we configured a named-based VirtualHost. The configuration above should be placed into a file with the .conf extension. On Debian-based distribution, such file should reside into the /etc/apache2/sites-available directory. For it to be “activated” a symlink to it should be created into /etc/apache2/sites-enabled directory, with the a2ensite command:

$ sudo a2ensite site1.lan.conf

If we are using a RHEL-based distribution, instead, the file should be placed under /etc/httpd/cond.d. In both cases the web server should be restarted for the configuration to be effective.

Let’s take a look at the directives we used in the example. First of all, with the *:80 notation we made so that the VirtualHost is used to respond to all requests on all IP on port 80. It would be good thing to recall how Apache works when multiple VirtualHost are defined: if Apache finds multiple VirtualHosts configurations which match a request IP-port combination, it checks if some of the matching VirtualHost is more specific, or in other words, if the request matches with the value of the ServerName directive. If none of the VirtualHosts are that specific, the first listed will be used to serve the request.

In the body of the configuration we used the following directives:

  • ServerName
  • DocumentRoot

With ServerName we basically set the hostname and port that server uses to identify itself, in this case site1.lan: this is what the user must write, for example, in the web browser to reach what is served by our VirtualHost.

The DocumentRoot directive, instead, is used to indicate the root directory which hosts the site document tree. In this case, the directory we previously created is /var/www/site1.lan.

How could we translate the above VirtualHost configuration to an Nginx Server Block? Here is what we could write:

server {
        listen *:80;
        server_name site1.lan;
        root /var/www/site1.lan;
}

At a first glance, we can already see the similarities between the two configurations. As you can see, a Server Block configuration is defined inside the Server { } stanza. The directives we used here are:

  • listen
  • server_name
  • root

The listen directive is used to set to what address and IP the Server Block will respond to and serve the request. In this case we only set *:80, which means that the Server Block will be used to respond to request on all IPs (* is a catch-all) on port 80.

Just as we did for the Apache VirtualHost, we defined the server name with the server_name directive: this establishes what Server Block is used to serve a specific request.

The root directive is the Nginx equivalent of the Apache DocumentRoot, and sets the root directories for the requests served by the Server Block.

Where should we place Nginx Server Block configuration in our filesystem? That, again, depends on the distribution we are using. On Debian and derivatives, we should create the configuration file inside the /etc/nginx/sites-available directory and then create a symlink inside /etc/nginx/sites-enabled. Supposing the configuration is stored in the site1.lan.conf file, we would run:

$ sudo ln -s /etc/nginx/sites-available/site1.lan.conf /etc/nginx/sites-enabled/

On Fedora and the other distributions which are part of the Red Hat family, instead, we just have to create the file inside the /etc/nginx/conf.d directory. In both cases we need to restart the Nginx server for the configuration to become effective.

Applying configuration to a specific section of the website

When we use Apache, to apply a set of instructions to a specific directory of the
site and all the files and directories contained in it, we use the <Directory>
directive. Here is an example of its usage:

<VirtualHost *:80>
    ServerName   site1.lan
    DocumentRoot /var/www/site1.lan
    <Directory /var/www/site1.lan/>
      # Directives here
    </Directory>
</VirtualHost>

The corresponding directive for an Nginx server block is location:

server {
        listen *:80;
        server_name site1.lan;
        root /var/www/site1.lan;

        location / {
            # Directives here
        }
}

In the case above we set a configuration for the root directory itself, so the directives will be applied to all the site files. Both the Apache Directory and the Nginx location directives can be repeated in order to fine-tune the configuration.

Specifying what files should be used as index

When configuring an Apache VirtualHost, we can use the DirectoryIndex directive to set what resources are used as index in a specific directory. For example, to use both the index.html and index.php files, we would write:

<VirtualHost *:80>
    ServerName   site1.lan
    DocumentRoot /var/www/site1.lan
    <Directory /var/www/site1.lan/>
      DirectoryIndex index.html index.php
    </Directory>
</VirtualHost>

In case multiple URLs are provided, as in this case, the server uses the first one that it finds. To provide the list of files which should be used as index inside a directory when we use Nginx and configure a Server Block, we want to use the index directive, instead:

server {
        listen *:80;
        server_name site1.lan;
        root /var/www/site1.lan;

        location / {
            index index.html index.php
        }
}

Just like happens when using Apache, the files are checked in the given order, so the first one to be found is used.

Enabling directory listing output

If we navigate to a site directory and none of the set index files doesn’t exists in it, we may want, in certain situations, to allow the web server to generate and display a list of the files existing in that directory (the default behavior is to deny access). To achieve such functionality we must use a specific directive: Options. This directive controls what server features are available in a specific directory. We use it to enable (with the + sign) the Indexes one:

<VirtualHost *:80>
    ServerName   site1.lan
    DocumentRoot /var/www/site1.lan
    <Directory /var/www/site1.lan/>
      Options +Indexes
    </Directory>
</VirtualHost>

Obtaining the same behavior with Nginx is also really simple. All we have to do is to use the autoindex directive, and set it to on:

server {
        listen 80;
        server_name site1.lan;
        root /var/www/site1.lan;

        location / {
                autoindex on;
        }
}


Restricting access to a resource

If we are using Apache, to restrict access to a resource served by a VirtualHost we can use the Require directive inside a Directory stanza. To allow access only from a specific subnet, for example 192.168.0.0/24, we would write:

<VirtualHost *:80>
    ServerName   site1.lan
    DocumentRoot /var/www/site1.lan
    <Directory /var/www/site1.lan/>
        Require 192.168.0.0/24
    </Directory>
</VirtualHost>

To deny access from that subnet, instead, we would write:

<VirtualHost *:80>
    ServerName   site1.lan
    DocumentRoot /var/www/site1.lan
    <Directory /var/www/site1.lan/>
    <RequireAll>
        Require all granted
        Require not 192.168.0.0/24
    </RequireAll>
    </Directory>
</VirtualHost>

This last example requires a little explanation. Why did we use the <RequireAll> directive? First all we must say that when configuring a VirtualHost access, we can use three “grouping” directives:

  • RequireAll
  • RequireAny
  • RequireNone

Those directives are used to group multiple access rules and they work this way:

Directive To be successful
RequireAll No directive must fail and at least one must succeed (directive can also be neutral)
RequireAny At least one directive must succeed
RequireNone No directive must succeed

If those directives are used to group a set of Require instructions, and here we just used one to negate access from an IP (a whole subnet in this case), why do we used RequireAll? That’s because when a require directive is negated (we used not), it can only fail or return a neutral result, therefore a request cannot be authorized on the base of a negated require alone. What we had to do is to put the negated Require inside a RequireAll directive, which in this case will fail since, as we stated above, for it to succeed, no directive inside of it must fail; that’s why we also put the Require all granted inside of it: to give it a change to succeed. If we don’t do this, we will receive the following error on server restart:

AH01624:  directive contains only negative authorization directives

The equivalent configuration for an Nginx Server Block can be obtained via the allow and deny directives. To allow access only from the subnet we used in the example above, we would write:

server {
        listen *:80;
        server_name site1.lan;
        root /var/www/site1.lan;

        location / {
            deny all;
            allow 192.168.0.0/24;
        }
}

To deny access to requests coming from the 192.168.0.0/24 subnet, instead:

server {
        listen *:80;
        server_name site1.lan;
        root /var/www/site1.lan;

        location / {
            deny 192.168.0.0/24;
        }
}

The ones above are only basic access control examples, but hopefully they give you an idea of how to convert the VirtualHost logic when using Nginx.

Specifying dedicated error and access log files

When we configure an Apache VirtualHost, we can make so that error logs for that specific resource are written into a dedicated file. The directive to use to achieve such functionality is ErrorLog, which accepts the path of the log file as argument:

<VirtualHost *:80>
    ServerName   site1.lan
    DocumentRoot /var/www/site1.lan
    ErrorLog "/var/log/httpd/site1.lan-error.log"
</VirtualHost>

Where the requests received by the server are logged, instead, is managed by the CustomLog directive. This directive accepts two mandatory arguments: the first is the
path of the file in which the logs will be written, the second specifies what will be written into the file. We define that using a format string. Let’s see an example:

<VirtualHost *:80>
    ServerName   site1.lan
    DocumentRoot /var/www/site1.lan
    ErrorLog "/var/log/httpd/site1.lan-error.log"
    CustomLog "/var/log/httpd/site1.lan-access.log" "%t %h %>s"
</VirtualHost>

Here we used the CustomLog directive so that accesses are logged into the /var/log/httpd/site1.lan-access.log file. The format string defines:

Notation Meaning
%t The time the request was received
%h The IP address of the request
%>s The final status of the request




A line in our access log file, in this case, would look like this:

[01/Oct/2021:23:49:56 +0200] 127.0.0.1 200

This is, of course, only a small subset of the symbols which can be used in the log description: you can take a look at the official documentation for the complete list.

To set the file Nginx will be use to log errors for a specific Server Block we can use the error_log directive:

server {
        listen *:80;
        server_name site1.lan;
        root /var/www/site1.lan;
        error_log "/var/log/nginx/site1.lan-error.log";
}

To set the file in which the access should be logged, instead, we use the access_log directive. By default the messages are stored in the default combined format, but this can be changed via the log_format directive. Since there is a default format already set, we can use the access_log directive by passing to it just the file path, for example:

server {
        listen *:80;
        server_name site1.lan;
        root /var/www/site1.lan;
        error_log "/var/log/nginx/site1.lan-error.log";
        access_log "/var/log/nginx/site1.lan-access.log";
}

Using the default log format, an access log line will look like this:

127.0.0.1 - - [01/Oct/2021:23:58:32 +0200] "GET / HTTP/1.1" 200 12 "-" "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:92.0) Gecko/20100101 Firefox/92.0"

Conclusions

In this tutorial we saw how to migrate Apache to Nginx using some of the most common VirtualHost setups to Nginx Server Blocks. We saw how to define the root and server name, how to restrict access to a resource, how to use resource-specific error and access logs, how to setup the files which should be used as index for a specific directory and how to allow the generation of a directory listing if such file doesn’t exist.

We also saw how to configure a VirtualHost/Server Block to respond to specifics IP:port requests. The ones listed above are only basic configurations, but hopefully they could represent a starting point. Please read both the Apache and Nginx documentations for a more in-depth knowledge!



Comments and Discussions
Linux Forum