Linux Server setup

Setting up a reverse Proxy Server

Having installed a mail server in the last chapter (E-mail forwarding), we will now set up a web server.

In this chapter we will set up a server service so that a static HTML page can be accessed via our domain. This preliminary work is also a prerequisite for obtaining TLS certificates from recognized certification authorities in the next chapter.

Since we have to expect that requests will not only ideally target an encrypted address (e.g. https://www.linuxserversetup.com), we have to additionally consider all other possibilities. Our domain, but also the IP address of the server, can be requested very differently via the HTTP protocol.

Legitimate examples:

Via port 80 (HTTP):

  • linuxserversetup.com
  • www.linuxserversetup.com
  • http://linuxserversetup.com
  • http://www.linuxserversetup.com

Via Port 443 (HTTPS):

  • https://linuxserversetup.com
  • https://www.linuxserversetup.com (Preferred address!)

Via the IP address:

  • 116.203.69.89 (Port 80)
  • http://116.203.69.89 (Port 80)
  • https://116.203.69.89 (Port 443)

As an example for this tutorial, I set https://www.linuxserversetup.com as the preferred address. Non-www requests should automatically redirect to the www address. All other variants should either redirect there or return an empty page. The latter is especially true if, for example, someone calls the server's IP from a browser.

So we need server software that responds to HTTP (80) and HTTPS (443) ports. Of course, our firewall must also allow these "public" ports through. For a simple "hello world" in the browser, we would basically only need a server service, such as NGINX or Apache, which sends a static HTML file back to the client. But if we're going to run our own server, let's also build an infrastructure that is scalable and can be used productively. Sooner or later, it must be possible to host additional domains and run them with different server-side technologies (backends).

The issue is that different server services (NGINX, Apache, Node.js, etc.) can run simultaneously but cannot share the same ports. For example, you can't have one domain running Apache and another domain running Node.js at the same time on port 80. If this is attempted, here's what would happen: Suppose Apache starts first and reserves port 80, Node.js starts after and tries to bind port 80 as well. This would fail with an error message ("Port allocated").

The solution is simple. A server service (reverse proxy) is placed in the foreground, which functions like a distribution list. The "outside world" communicates only with this reverse proxy. It can filter external requests by IP, port, domain, subdomain or even URL and forward them to specified internal ports. The corresponding backend will then respond to them. This works because backends or internal server services are bound to different internal ports, which allows them to run in parallel.

The reverse proxy will also be responsible for encrypting the data transmission to the outside. Thus, we have a central body that is responsible for issuing the TLS certificates.

Further advantages are: Load balancing and caching.

I will set up the two addresses linuxserversetup.com and dev.linuxserversetup.com. Other domains or subdomains would simply follow the scheme.

Before we start, let's do one sensible thing at this point, so that we don't get any problems with write permissions on the web directories later on. It is common to create web projects under /var/www. We will have to create this folder with sudo. But since we will mainly work with tom later, we set him as the new owner for this folder. So he is authorized to edit files and folders there.

We create /var/www with mkdir:


__$ sudo mkdir /var/www
 

We change the owner with the chown command as before:


__$ sudo chown -R tom:tom /var/www
 

Next steps:


Install NGINX

As a reverse proxy we use NGINX. NGINX is versatile, very robust and easy to configure. In the meantime, the software is one of the most used web server services worldwide.

We install NGINX as usual with apt:


__$ sudo apt install -y nginx
 

After the installation we can retrieve the current version:


__$ nginx -v
 

Also, nginx has created a folder html under /var/www with an HTML file in it:


__$ ls -l /var/www
__$ ls -l /var/www/html
__$ less /var/www/html/index.nginx-debian.html
 

/var/www/html/index.nginx-debian.html


<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
  body {
    width: 35em;
    margin: 0 auto;
    font-family: Tahoma, Verdana, Arial, sans-serif;
  }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p>
<p>For online documentation and support please refer to <a href="http://nginx.org/">nginx.org</a>.<br/>Commercial support is available at <a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>

We don't need the folder and its content or we will replace it with something else later. So we can delete /var/www/html including its content. Recursively delete with rm -r:


__$ sudo rm -r /var/www/html
 

The global settings for NGINX are in the file /etc/nginx/nginx.conf:


__$ sudo nano /etc/nginx/nginx.conf
 

Here we can comment in server_tokens off. This prevents the server version from being written to the response header. Otherwise, the line Server: nginx/1.18.0 (Ubuntu) could be read, for example, via the web developer console of a browser. This information is hidden for security reasons.

In this file we can also enable gzip and all other settings related to it (starting with gzip_). gzip compresses data for transmission, thus saving bandwidth and increasing transmission speed.

The full /etc/nginx/nginx.conf should look like this:

/etc/nginx/nginx.conf


user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
  worker_connections 768;
  # multi_accept on;
}

http {

  ##
  # Basic Settings
  ##

  sendfile on;
  tcp_nopush on;
  tcp_nodelay on;
  keepalive_timeout 65;
  types_hash_max_size 2048;
  server_tokens off;

  # server_names_hash_bucket_size 64;
  # server_name_in_redirect off;

  include /etc/nginx/mime.types;
  default_type application/octet-stream;

  ##
  # SSL Settings
  ##

  ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
  ssl_prefer_server_ciphers on;

  ##
  # Logging Settings
  ##

  access_log /var/log/nginx/access.log;
  error_log /var/log/nginx/error.log;

  ##
  # Gzip Settings
  ##

  gzip on;

  gzip_vary on;
  gzip_proxied any;
  gzip_comp_level 6;
  gzip_buffers 16 8k;
  gzip_http_version 1.1;
  gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

  ##
  # Virtual Host Configs
  ##

  include /etc/nginx/conf.d/*.conf;
  include /etc/nginx/sites-enabled/*;
}

#mail {
  #  # See sample authentication script at:
  #  # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
  #
  #  # auth_http localhost/auth.php;
  #  # pop3_capabilities "TOP" "USER";
  #  # imap_capabilities "IMAP4rev1" "UIDPLUS";
  #
  #  server {
  #     listen     localhost:110;
  #     protocol   pop3;
  #     proxy      on;
  #  }
  #
  #  server {
  #     listen     localhost:143;
  #     protocol   imap;
  #     proxy      on;
  #  }
  #}

There is also defined where the log files for NGINX are stored. There should be currently empty:


__$ sudo less /var/log/nginx/access.log
__$ sudo less /var/log/nginx/error.log
 

In order for the changes to be applied, we restart NGINX:


__$ sudo systemctl restart nginx
 

To check the current configuration, we specify the -t parameter:


__$ sudo nginx -t
 

If everything is in order, the output looks like this:


nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Let's also take a quick look at the current status:


__$ sudo systemctl status nginx
 

If there are no problems, the NGINX status should be active (running).

With the following command NGINX starts automatically with every server restart:


__$ sudo systemctl enable nginx
 

What remains is the release through the firewall:


__$ sudo ufw allow 'Nginx Full'
 

With this, we would have finished the basic configuration for our reverse proxy.

Next, we create individual server blocks that we can use to separate external requests.


Server blocks

NGINX works with configuration units, so-called server blocks. Even if it would be possible to write the entire NGINX configuration in a file, it is more meaningful and clearer to have individual purpose-bound files.

As with other server services, interaction between the sites-available and sites-enabled folders is recommended.

Configuration files are placed in the /etc/nginx/sites-available folder. In fact, of those, only those that are included via /etc/nginx/sites-enabled, using symbolic links (symlinks: ln), are considered by NGINX. This may seem a bit excessive at the beginning with only one domain, but it is an important basic order when more addresses are added.

The links in sites-enabled provide a good overview, even if things get a bit chaotic in sites-available.


Default Server Block

With the first server block we will intercept and handle all HTTP requests that cannot be matched. For example, if the server IP http://116.203.69.89 is called via the browser. For this case I basically want to display an empty page.

NGINX has already created a "default". The symbolic link is in /etc/nginx/sites-enabled:


__$ ls -l /etc/nginx/sites-enabled
 

Output:


lrwxrwxrwx 1 root root 34 Jan  3 19:15 default -> /etc/nginx/sites-available/default

The actual configuration file (default) is accordingly located in /etc/nginx/sites-available:


__$ ls -l /etc/nginx/sites-available
 

Output:


-rw-r--r-- 1 root root 2416 Jan  3  2022 default

Before we edit the default file, we make a backup copy of it:


__$ sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/default-backup
 

Now we can open the default file:


__$ sudo nano /etc/nginx/sites-available/default
 

And replace the entire contents with the following (TIP: CTRL+k to delete entire lines):

/etc/nginx/sites-available/default


server {
  listen      80;
  listen      [::]:80;
  server_name _;

  root        /var/www/default/;
  index       index.htm;

  location / {
    try_files $uri $uri/ /index.htm;
  }

  location = /favicon.ico { access_log off; log_not_found off; }
}
 

To explain:

  • server starts the server block.
  • listen responds to the IPv4 and IPv6 address with port 80 each.
  • server_name has a placeholder (_). A domain name could also be entered here.
  • root points to a project path.
  • index names a default initial file.
  • location / accepts all URL paths. This can be http://116.203.69.89, but also http://116.203.69.89/a/b/c. If the overhang $uri by try_files fails, /index.htm is returned (which will always be the case for this default page!).
  • location = /favicon.ico takes into account that browsers generally request a favicon. Since none is provided here and we don't want to log the 403 and 404 errors, we disable logging with access_log and log_not_found for the path /favicon.ico.

We still need to create the root path:


__$ mkdir /var/www/default
 

And also the file for index:


__$ nano /var/www/default/index.htm
 

An empty page is enough for me here. So the content of the index.htm could be a valid HTML framework:

/var/www/default/index.htm


<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>default index</title>
  </head>
  <body></body>
</html>
 

After saving we check the new configuration and restart NGINX:


__$ sudo nginx -t
__$ sudo systemctl restart nginx
 

If there are no problems, we can now call the empty page via http://116.203.69.89 in the browser.

Since a placeholder is entered as server_name, the domain call (http://linuxserversetup.com) is also intercepted by this server block. This changes with the next section.


Server block for a domain (force www)

With this server block we configure the addresses http://linuxserversetup.com and http://www.linuxserversetup.com. Where automatically redirected to www. We create a new configuration file under /etc/nginx/sites-available, for which we also store a symbolic link in /etc/nginx/sites-enabled.

It makes sense to name the file similar to the address, only that it starts with the top-level domain, followed by the domain and possibly subdomain. As a configuration file it should also end with .conf. In an alphabetically ordered list, this keeps the same domain names below each other.


__$ sudo nano /etc/nginx/sites-available/com.linuxserversetup.conf
 

The content is similar to before, except that we now specify two addresses for server_name and the root folder /var/www/com.linuxserversetup. We also assign the parameter default_server, which tells NGINX to prioritize this server block. The first server block forces the redirection to www. It is recommended to use $server_name instead of $host because it may be manipulated by the user. If multiple addresses are entered under server_name, the first one is taken.

/etc/nginx/sites-available/com.linuxserversetup.conf


# redirect to www
server {
  listen      80;
  server_name linuxserversetup.com;
  return      301 $scheme://www.$server_name$request_uri;
}

# main block
server {
  listen      80 default_server;
  listen      [::]:80;
  server_name www.linuxserversetup.com;

  root        /var/www/com.linuxserversetup/;
  index       index.htm;

  location / {
    try_files $uri $uri/ /index.htm;
  }

  location = /favicon.ico { access_log off; log_not_found off; }
}
 

We also create a folder for the web project and name it according to the convention com.linuxserversetup:


__$ mkdir /var/www/com.linuxserversetup
 

Vor now we also place an index.htm there. This will be changed later, depending on the web technology:


__$ nano /var/www/com.linuxserversetup/index.htm
 

This can also be an empty HTML page. I add a title, so that it can be better distinguished from the previous standard server block:

/var/www/com.linuxserversetup/index.htm


<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>linuxserversetup.com</title>
  </head>
  <body></body>
</html>
 

Now we still create the symbolic link in the folder /etc/nginx/sites-enabled to the configuration file com.linuxserversetup.conf with the command ln:


__$ sudo ln -s /etc/nginx/sites-available/com.linuxserversetup.conf /etc/nginx/sites-enabled/
 

Finally, we check and restart NGINX to apply the new configuration:


__$ sudo nginx -t
__$ sudo systemctl restart nginx
 

Nginx should now be able to distinguish between IP and domain requests.

We can test this with a browser. The address http://116.203.69.89 should show the "default" title. http://linuxserversetup.com and http://www.linuxserversetup.com show a different title.


Subdomain server block (dev)

Every web project should have a development or test page. We had already prepared the dev subdomain in the DNS records chapter.

The procedure is identical to the previous section, actually only the names change.

Create the .conf file:


__$ sudo nano /etc/nginx/sites-available/com.linuxserversetup.dev.conf
 

The contents of com.linuxserversetup.dev.conf:

/etc/nginx/sites-available/com.linuxserversetup.dev.conf


server {
  listen      80;
  listen      [::]:80;
  server_name dev.linuxserversetup.com;

  root        /var/www/com.linuxserversetup.dev/;
  index       index.htm;

  location / {
    try_files $uri $uri/ /index.htm;
  }

  location = /favicon.ico { access_log off; log_not_found off; }
}
 

Create the folder under /var/www/:


__$ mkdir /var/www/com.linuxserversetup.dev
 

Create the index.htm:


__$ nano /var/www/com.linuxserversetup.dev/index.htm
 

The contents of the index.htm:

/var/www/com.linuxserversetup.dev/index.htm


<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>dev.linuxserversetup.com</title>
  </head>
  <body></body>
</html>
 

Create the symbolic link in the /etc/nginx/sites-enabled folder:


__$ sudo ln -s /etc/nginx/sites-available/com.linuxserversetup.dev.conf /etc/nginx/sites-enabled/
 

Check and restart NGINX:


__$ sudo nginx -t
__$ sudo systemctl restart nginx
 

We can test the address http://dev.linuxserversetup.com again with the browser.

Later, we will further expand the configuration files and redirect them to different backends.

Next, we will include TLS certificates and enforce the HTTPS protocol.