Hide
Scraps
RSS

Setting up a Nim server for dummies

8th June 2023 - Guide , Linux , Nim , Web

Nim is a great candidate for server development, but if you want to run your own server and are new to the world of server management or Linux there can be a daunting amount of information to consume in order to do it right. In this article I’ll give a quick rundown of setting up a small server capable of running your Nim server in a safe way. It is much more low-level than many of my other articles and includes a lot of command line snippets and configuration file details, so feel free to bookmark it and come back to it when you need it. I will also try to keep it up to date and add any recommendations from the Nim community. This article requires that you are comfortable with basic Linux tasks like running commands and editing files from the shell (with Vim, Emacs, Nano, etc.)

Start with setting up a machine, virtual or otherwise, and install some flavour of Linux. This guide is based on the LTS release of Ubuntu (22.04 at the time of writing), running on the lowest Linode tier ($5 a month 1GB RAM, shared CPU). This machine is chosen simply because it’s the one I happened to be setting up when I was writing this, but feel free to use another service like Hetzner for example which at the time of writing offers much more for an even cheaper price. If you know what you’re doing in terms of Linux stuff feel free to pick any distro, but if you’re new to Linux I would recommend starting with Ubuntu LTS as that is what all these commands are written for.

You probably also want to set up a domain name, or sub-domain name for your service. Simply add the IPv4 and IPv6 addresses of your server as A and AAAA record respectively in your domains DNS configuration. This differs a lot for each registrar, so how exactly to do it for your registrar is not covered here. Note that you can also run more than one Nim server on the same machine, if that is the case create multiple sub-domains and point them both to the same IP.

Hardening

After the server is installed and booted up it’s a good idea to set up a firewall. Simply SSH into the server (Linode does this as root, we’ll fix that in a second), run apt install nftables and when that is done systemctl start nftables && systemctl enable nftables to start and enable the service. To configure it you can use this default configuration, simply put it in a file like /etc/nftables/firewall.conf and run nft -f /etc/nftables/firewall.conf to enable it:

flush ruleset                                                                    
                                                                                 
table inet firewall {
        # Reply to ping on both ipv4 and ipv6, but ratelimit them
    chain inbound_ipv4 {
        icmp type echo-request limit rate 5/second accept
    }
    chain inbound_ipv6 {
        # Accept neighbour discovery otherwise connectivity breaks
        icmpv6 type { nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert } accept
        icmpv6 type echo-request limit rate 5/second accept
    }

    chain inbound {
        # By default, drop all traffic that doesn't match our rules below
        type filter hook input priority 0; policy drop;

        # Allow traffic from established connections and related connections, but drop invalid ones
        ct state vmap { established : accept, related : accept, invalid : drop }

        # Allow loopback traffic, this means traffic between services on the same machine
        iifname lo accept

        # This connects the above icmp/ping rules
        meta protocol vmap { ip : jump inbound_ipv4, ip6 : jump inbound_ipv6 }

        # Allow SSH on port TCP/22 and allow HTTP(S) TCP/80 and TCP/443 for IPv4 and IPv6.
        tcp dport { 22, 80, 443} accept
    }

    chain forward {
        # Don't allow forwarding (forwarding would be used for something like a router)
        type filter hook forward priority 0; policy drop;
    }
}

If you already have your server built locally and running on some other port like 5000 or 8080 don’t add this port in the allowed ports list in the configuration, we will add what’s called a reverse proxy which handles this for us. The reverse proxy will be the one talking to the wider internet on port 80 or 433 and then it talks to our server over the loopback interface (which you can see we have allowed above).

While you’re logged in as root you can also grab Nginx and Let’s Encrypt’s certbot, along with the script to integrate the two: apt install nginx certbot python3-certbot-nginx. Certbot is a tool that we’ll use to enable HTTPS on our server, and to keep our keys up to date. It will automatically get our keys and reconfigure Nginx to use them. If you know what you’re doing and want more control you can use the dehydrated package instead, but it requires a bit more manual setup of configuration files.

Now let’s create a user for your server and disable login as root. First we need to add a user which has permissions to elevate to root.

useradd -m -s /bin/bash server # Create a user called server with a home directory and the bash login shell
passwd server # Create a password for our user, you will be prompted to write one when running this command
usermod -a -G sudo server # Add the server user to the sudo group

In order for the usermod step to give us access to use the sudo command you must have a line like this in your /etc/sudoers file. It is recommended to do this with visudo as messing up this file can cause you to get locked out of your system. In this case though it’s a pretty fresh install, so if you don’t like Vim and want to edit in another editor you could take the risk:

%sudo   ALL=(ALL:ALL) ALL

The Ubuntu LTS should already have this, but if you don’t have that, or have a similar line but with %wheel instead then add it or change the group name to whatever comes after the %. If there is no percentage sign before the name its a user and not a group, and if there is no ALL:ALL part then you might not have to use sudo to do root stuff (which is bad).

Now log in to your new user with su server and check that you can’t run a command like cat /etc/sudoers but that you’re able to access it with sudo cat /etc/sudoers. Also make sure you can SSH directly into the server user (by running something like ssh server@myserver.com). If everything works we can disable login for the root user. Simply open up the file /etc/ssh/sshd_config and change the line:

PermitRootLogin yes

to

PermitRootLogin no

Then restart the SSH daemon service with systemctl restart sshd, this disables the root user for direct log-in and have to log in through our limited access user instead.

Logging in with a password over SSH isn’t the best security wise though, so it is recommended to log in with a SSH key instead. If you don’t already have one, you can generate one with the ssh-keygen command (on your normal machine, not on the server). A SSH key is comprised of two parts, the private and the public part. We’ll upload the public part to the server and keep the private part secure. Then the server can use the public part to decrypt things encrypted by the private part and vice-versa and we can use this to log in to our server. I recommend setting a password for your SSH key when you generate it, but it is not strictly necessary if you know no one else will have access to the private key file.

Upload your public key to the server by using ssh-copy-id server@myserver.com or copy it manually into the .ssh/authorized_keys folder and set the appropriate permissions. The private key needs to be placed somewhere that your ssh command will find it. On Linux ssh-keygen defaults to placing your newly generated key in such a place, but for other OSes this might not be the case.

Once you have the key in place verify that you can SSH into it by using the keyfile. If the file was placed somewhere SSH is able to find it (such as ~/.ssh/id_rsa on Linux), you can just run your regular ssh command. Otherwise you can pass it manually by running something like:

ssh -i <path to keyfile> server@myserver.com

You should be asked for the password for your SSH key (not for the user that we created earlier) and once entered you will be logged in to the server. When you’re certain that this works open /etc/ssh/sshd_config and set this line:

PasswordAuthentication no

And restart the SSH daemon with:

sudo systemctl restart ssh

After doing this, you won’t be able to log in to the server without your private key, so make sure to keep a backup of it somewhere safe!

Now that we’ve done some basic server hardening, let’s set up the actual web server!

Reverse proxy and HTTPS

Nim web-servers often rely on the HTTP traffic being well-formed, and in order to have HTTPS working we will need to deal with certificates. In order to protect ourselves slightly from bad actors on the web and to not have to deal with implementing HTTPS and multi-domain routing in our Nim code we will use a reverse proxy. This is basically just a web-server whose only job is to verify messages, handle the HTTPS stuff, and pass the traffic on to us. For this article we’ll use Nginx, but you could also do the same with something like Apache.

To set up Nginx (which we installed earlier) first disable the default site with unlink /etc/nginx/sites-enabled/default then open a configuration file in /etc/nginx/conf.d/myserver.com.conf (replace the myserver.com part with the (sub-)domain name of your server). In this example we’ll use our Nim server to serve the static files as well, so everything will be passed through the Nim server. This will host our configuration which is fairly simple:

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name myserver.com www.myserver.com; # Various names for our server

    location / {
        proxy_pass http://localhost:8080; # Pass all traffic on to port 8080 on our server
        proxy_send_timeout 600;
        proxy_set_header Host $host; # Add on some extra headers in case we need them
        proxy_set_header X-Real_IP $remote_addr;
    }
}

If you are setting up more than one Nim server on the same machine and want to point to them with different (sub-)domain names note that you can only have one marked as default_server. Simply remove the default_server part of the above config for all but one of your configurations. To set the (sub-)domain name for each file change the filename and the server_name entry to the correct domain name. If you want to use Nginx to serve your static files (which will very likely give you a performance increase) you can add a block like this to the server block in the above configuration:

location /assets/ {
    root /www/assets
}

This will serve myserver.com/assets from the /www/assets folder on the server. If you place files here and they aren’t accessible you might have to change the permissions of the files with chmod o+r to make them readable to the others category.

After saving your configuration file(s) run nginx -t && nginx -s reload to restart the server. It will now proxy anything from port 80 targeted at myserver.com to port 8080 on the local machine (unless you’ve changed the above configuration with other port numbers). But it still only does HTTP, to get HTTPS working run certbot --nginx -d myserver.com -d www.myserver.com to create certificates and update the configuration file to redirect to HTTPS. After that completes you can see in the configuration file you just created that certbot has added the various certificate directives and registered all the required certificates. Run this step for each of your (sub-)domains. These certificates needs to be refreshed from time to time, so you probably also want to add something like this to your crontab (with crontab -e):

0 12 * * * /usr/bin/certbot renew --quiet

This entry runs a check every day at noon to make sure that the certificates are up to date. Also make sure that the cron service is running with systemctl status cron, or enable and start it with systemctl enable cron && systemctl start cron.

Building our Nim server

Nginx is now set up to only allow HTTPS requests on our domain, and to pass all the requests on to localhost:8080. Now it’s time to set up our actual Nim server! You probably already have a Nim server program that you want to deploy, so I won’t go into how to actually write a Nim server here. If you haven’t written a server program yet, you can quickly get started with Jester, HappyX, Prologue, or any other of the myriad of HTTP servers. You can also have a look at Federico’s project maker which will set everything up as a proper .deb package.

If you’ve got your code ready, but haven’t got a proper package, we need to build it for the architecture and OS we are on. If you run a Linux machine locally you can probably just copy the binary to the server with scp mynimserver server@myserver.com:mynimserver, otherwise you’ll either have to cross-compile it, or just install Nim on the server and build the website there. To install Nim I recommend using choosenim:

curl https://nim-lang.org/choosenim/init.sh -sSf | sh

Once that command completes you will have the latest stable release of Nim installed on your server. If you want to update it later on simply run choosenim update stable. If you have your project set up in a Git repository and as a Nimble package the easiest solution now is to simply run a git clone <repo URL> and then a nimble build -d:release. To make sure your Nim server is run when the machine starts and that it will restart if it fails for some reason we can add a service file for it. If your username is server and your project is called mynimserver and cloned into the home directory (the one you are dropped in when you SSH into the server) such a service file would look a bit something like this:

[Unit]
Description=Our awesome Nim server

[Service]
User=server
WorkingDirectory=/home/server/mynimserver
ExecStart=/home/server/mynimserver/mynimserver
Restart=always
RestartSec=5

# Sandboxing features
PrivateTmp=yes
NoNewPrivileges=yes
ProtectSystem=strict
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_DAC_READ_SEARCH
RestrictNamespaces=uts ipc pid user cgroup
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
PrivateDevices=yes
RestrictSUIDSGID=yes
IPAddressAllow=192.168.1.0/24
MemoryDenyWriteExecute=yes
LockPersonality=yes

[Install]
WantedBy=default.target

This sets the working directory to the mynimserver folder, which is probably where you binary expects to find the static files to serve, and runs the mynimserver binary within thin folder. It also contains some sandboxing features which can help protect your server if you’ve inadvertently added the possibility for outside users to execute things in your program. The sandboxing features here should be fine for most things you want to do, but just remember that if you’re doing something a bit more esoteric you might have to disable some of these. There are also a lot more you could add (just see the output from systemd-analyze security mynimserver) if you want to make it extra secure, but this is a rabbit hole that’s too deep for this article.

To install this service simply copy this file to /etc/systemd/system/mynimserver.service and run systemctl daemon-reload and systemctl enable mynimserver && systemctl start mynimserver. If everything has gone to plan your website should now be up and running! Simply point your browser to https://myserver.com (or whatever your domain is) and watch your website in all its glory!

UPDATE 13/06/2023: Improved the guide with feedback from Federico Ceratto and Stefan Salewski