ZNC, the right way

I’ve setup ZNC one too many times.

Sometimes I forget it’s riding shotgun on a spare droplet heading to the trash heap. Other times, my payment method expires and so too does the instance. Other times I’m too lazy to host it in the cloud at all, so I run it locally. In any case, today I wanted to set up ZNC the right way… for the last time.

I also want to document the process for posterity and stop scouring the web for the same articles time after time.

The TODO list for today:

  • Setup a dedicated domain
  • Provision a dedicated droplet, hosted on DigitalOcean
  • Configure separate listeners for IRC and HTTP traffic
  • Generate an SSL cert with LetsEncrypt
  • Setup Nginx to terminate SSL traffic and proxy to ZNC

Dedicated domain and droplet

I’ll gloss over the relatively simple steps like provisioning a droplet, securing the firewall, installing ZNC, and purchasing a domain.

tldr; I…

  1. Provisioned a droplet.
  2. Purchased a new domain. I opted for a .chat TLD because I thought it was appropriate
  3. Directed the registrar to DigitalOcean’s nameservers. Consolidating behind a single control panel makes life much easier.
  4. Created an A record with an irc subdomain pointing at the IP of my new droplet.

For the remainder of this post, I’ll use irc.example.chat as my placeholder domain!

Configuring ZNC

How you configure ZNC is a matter of personal taste. I opt to load fairly standard modules like chanserver, fail2ban, log, and identfile but feel free to go crazy! One thing that is important to mention though, are the separate listeners.

I created one listener for SSL IRC traffic over 6697 and one listener for non-SSL HTTP traffic over 8080. The web listener has SSL disabled because 1) it’s only a self signed cert 2) it’s only hosting to localhost.

<Listener listener0>
    AllowIRC = true
    AllowWeb = false
    IPv4 = true
    IPv6 = false
    Port = 6697
    SSL = true
    URIPrefix = /

<Listener listener1>
    AllowIRC = false
    AllowWeb = true
    Host = localhost
    IPv4 = true
    IPv6 = false
    Port = 8080
    SSL = false
    URIPrefix = /

Configuring Nginx

I’ll first preface this section by saying: I’m not an Nginx wizard by any means. In fact, most of this configuration comes from the Nginx blog and Stack Overflow.

Before we can generate a certificate, we want to add a basic configuration. I dropped a file in /etc/nginx/config.d and create a softlink to sites-available and sites-enabled.

touch /etc/nging/config.d/irc.example.chat
ln -s /etc/nginx/config.d/irc.example.chat /etc/nginx/sites-available
ln -s /etc/nginx/config.d/irc.example.chat /etc/nginx/sites-enabled

I then edited the parent configuration. Fortunately, it’s fairly readable; nginx will proxy all SSL traffic from irc.example.chat to our ZNC localhost listener. We can also set a few headers in the process.

server {
    listen      443 ssl http2;
    server_name irc.example.chat;
    access_log  /var/log/nginx/irc.log combined;

    location / {
        proxy_set_header      Host             $host;
        proxy_set_header      X-Real-IP        $remote_addr;
        proxy_set_header      X-Forwarded-For  $proxy_add_x_forwarded_for;
        proxy_set_header      X-Client-Verify  SUCCESS;
        proxy_set_header      X-Client-DN      $ssl_client_s_dn;
        proxy_set_header      X-SSL-Subject    $ssl_client_s_dn;
        proxy_set_header      X-SSL-Issuer     $ssl_client_i_dn;
        proxy_read_timeout    1800;
        proxy_connect_timeout 1800;

The ssl_certificate configs will be added by certbot in the next step. If they aren’t added for whatever reason, they should look something like…

ssl_certificate     /etc/letsencrypt/live/irc.example.chat/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/irc.example.chat/privkey.pem;

Generating certs with LetsEncrypt

Now the fun part, and the reason to setup the domain in the first place. I used the EFF’s handy certbot with Nginx drivers to provision a cert with LetsEncrypt. Technically the Nginx drivers aren’t necessary – you could provision the certs directly – but the added config editor is a nice feature.

certbot took care of just about everything!

sudo apt-get install certbot python3-certbot-nginx

certbot --nginx -d irc.example.chat

I say “just about” because these certs still expire every 90 days. I’m guaranteed to forget about the cert, so I set a cron job (sudo crontab -e) to renew the cert every week.

0 0 * * 0 certbox renew --quiet

Configuring Weechat

The last step of any ZNC install is to setup your client. I use Weechat, so the next steps may be different for you.

Weechat needs to validate ZNC’s SSL cert to connect over 6697, so grab the SSL certificate fingerprint from the droplet first.

cat ~/.znc/znc.pem \
    | openssl x509 -sha512 -fingerprint -noout \
    | tr -d ':' \
    | tr 'A-Z' 'a-z' \
    | cut -d = -f 2

On the weechat client, I added the ZNC server with a default network, set the fingerprint, connected, and saved my changes. One detail that I forget constantly: these creds aren’t your network creds, they’re your ZNC creds.

/server add ZNC irc.example.chat/6697 -ssl -username=username/network -password=password
/set irc.server.ZNC.ssl_fingerprint <fingerprint>
/connect ZNC

Most networks require you to authenticate with SASL these days, which I set through Weechat. Another option is to load the SASL module and set your credentials through the web console.

/msg *Status LoadMod sasl
/msg *SASL Set nick pass
/msg *SASL RequireAuth true

And that’s about it. We’ve setup the A record for our domain, configured separate HTTP and IRC listeners for ZNC, generated an SSL cert through LetsEncrypt, proxied web traffic to ZNC with Nginx, and connected securely with Weechat. A pretty productive afternoon!

If you’d like to chat, you can find me on libera.chat!