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.