Since our generic proxy approach does not work out of the box for the niltalk chat server, we will see in this post how to put everything together for this case.

We assume thet we will run the talk server at the address https://talk.example.com.

Installing niltalk

Let’s login to out target machine and be sure, that we have no open port 9000 to the outer world.

If we do not want to build the server by ourselves, we get the binary:

user@talk.example.com:/home/user$ wget https://github.com/knadh/niltalk/releases/download/v0.1.1/niltalk_0.1.1_linux_amd64.tar.gz

After unpacking, we create a target directory

user@talk.example.com:/home/user$ sudo mkdir /opt/niltalk

and copy the binary there

user@talk.example.com:/home/user$ sudo cp niltalk /opt/niltalk

For the static web templates, we need to clone the niltalk git-respository

user@talk.example.com:/home/user$ git clone https://github.com/knadh/niltalk.git

and to copy the static directory also to the target folder:

user@talk.example.com:/home/user$ sudo cp niltalk/static /opt/niltalk

To create a configuration, we have to run the server with the command

user@talk.example.com:/opt/niltalk$ sudo niltalk --new-config

This creates a file config.toml that we can amend later on as needed. For now, it makes sense to set the storage to memory:

# Storage kind, one of redis|memory|fs.
storage = "memory"

# Redis cache server.
# Rooms are cached until they expires. Messages are not cached.
#[store]
#address = "redis:6379" # Eg: 127.0.0.1:6379
#password = ""
#db = 0
#active_conns = 100
#idle_conns = 20
#timeout = "3s"

#prefix_room = "NIL:ROOM:%s"
#prefix_session = "NIL:SESS:ROOM:%s"

# InMemory store config.
[store]
# no options available.

# FileSystem store config.
# [store]
# path = "db.json"

At this point we have the chat server installed. Running it by typing sudo /opt/nilserver/nilserver should result in an open port 9000.

Creating the service

Since we want to start the talk server by default, we create a service file /lib/systemd/system/niltalk.service with the following contents:

[Unit]
Description=niltalk server
After=multi-user.target

[Service]
WorkingDirectory=/opt/niltalk
ExecStart=/opt/niltalk/niltalk --static-dir=/opt/niltalk/static
ExecReload=/bin/kill -HUP $MAINPID

[Install]
WantedBy=multi-user.target

We enable the service by linking it to the right place:

user@talk.example.com:/etc/systemd/system/multi-user.target.wants$ sudo ln -s /lib/systemd/system/niltalk.service .

Now we should be able to start and stop the service using the sudo service niltalk start and sudo service niltalk stop commands, respectively.

Apache reverse proxy

The standard approach does not work here. The talk server is using web sockets, which are not proxied/rewritten by default. So we have to amend our configuration to do that.

But first, we can go with the “standard” approach to get the certificate. The Apache talk.example.com.conf configuration reads as

<VirtualHost *:80>
        ServerAdmin webmaster@localhost
        ServerName talk.example.com
        ServerAlias talk.example.com
        ProxyPreserveHost On
        ProxyPass / http://127.0.0.1:9000/
        ProxyPassReverse / http://127.0.0.1:9000/
</VirtualHost>

So we get our certificate via sudo certbot --apache -d talk.example.com.

But now we have to add some configuration on the newly created talk.example.com-le-ssl.conf:

<IfModule mod_ssl.c>
<VirtualHost *:443>
        ServerAdmin webmaster@localhost
        ServerName talk.example.com
        ServerAlias talk.example.com

        ProxyPreserveHost On
        ProxyRequests Off
        RewriteEngine On

        RewriteCond %{HTTP:UPGRADE} ^WebSocket$           [NC,OR]
        RewriteCond %{HTTP:CONNECTION} ^Upgrade$          [NC]
        RewriteRule .* ws://127.0.0.1:9000%{REQUEST_URI}  [P,QSA,L]
        RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
        RewriteRule .* http://127.0.0.1:9000%{REQUEST_URI} [P,QSA,L]
        RequestHeader set X-Forwarded-Proto "https"

        <Location />
                Require all granted
                ProxyPassReverse http://127.0.0.1:9000/
                ProxyPassReverseCookieDomain 127.0.0.1 talk.example.com
        </Location>

SSLCertificateFile /etc/letsencrypt/live/talk.example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/talk.example.com/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>

So basically, we are enabling the websocket use for our proxy.

In order to avoid client reconnection every 5 minutes, we use a (maybe dirty, I’m open for better solutions) workaround and set the connection timeout in /etc/apache2/apache2.conf to 12 hours:

Timeout 43200

In order to get this all working, we have to enable the Apache headers module and to restart the webserver:

$ sudo a2enmod headers
$ sudo service apache2 restart

Finally, we have to set the right IP and URL in our niltalk config file /opt/niltalk/config.toml:

[app]
# Address to listen, use "tor" to run an hidden service.
# address = "0.0.0.0:9000"
address = "127.0.0.1:9000"

# No trailing slashes.
root_url = "https://talk.example.com"

After restarting the chat server by

$ sudo service niltalk stop
$ sudo service niltalk start

we are done.

That’s it! Now we have a niltalk chatserver behind an Apache reverse https proxy.

Main source for the Apache config part: mattermost.