Join the IRC Idle-Party over Tor with ZNC on OpenBSD

After having successfully set up an IRC server as Tor Hidden Service on OpenBSD for you and your friends, let’s find out how you can outsource idling on your IRC server (and other networks!) to ZNC.

Join the IRC Idle-Party over Tor with ZNC on OpenBSD

As some of you noticed, I have published a brief write-up on how to host your own IRCv3 server as a Tor Hidden Service on OpenBSD a while ago. At the very end of the post I mentioned how I would usually set up an IRC bouncer on a cloud instance and have it connect to IRC instead of directly connecting from a client. I do that in order to not have to A) set up connections individually on each of my devices and B) have an additional layer for privacy and security purposes between myself and the IRC services. In order to explain in detail how this can be accomplished, let’s set up our own ZNC bouncer on OpenBSD, that connects to IRC services via Tor!


In order to explain what an IRC bouncer is in first place, we have to first understand how IRC servers function and how they are different from e.g. Discord or Slack.

When you log in to Discord, your client is provided with a view of what has happened on the server while you weren’t looking, followed by what is happening right now, meaning that you’re able to scroll back and read what someone said two days ago, even if you weren’t online at that time. That is because Discord stores every message everyone ever sent and provides a custom, per-user view on those messages. IRC servers on the other hand usually don’t do that. On IRC your user (or better, client) is sent the messages while you’re connected to the server. If you’re not connected (meaning, not online), you won’t be receiving anything. To simplify things, IRC can basically be thought of as an ephemeral message distribution hub, while something like Discord or Slack on the other hand is more similar to an actual database that stores information and from which clients can query data when they need it.

In order to emulate the behavior you’re getting for free (free as in you being the product) on Discord, you can use an IRC bouncer. A bouncer is basically a headless client that’s connecting in your name to an IRC server and that stays connected to it 24/7. This way your user on that IRC server appears online and the server will forward it communication that’s happening across the channels you’ve joined.

On the other side, the bouncer listens on a port to which you can connect to using a regular IRC client, so that you are able to participate on the IRC servers that the bouncer is in turn connected to. The bouncer is sort of a proxy, or middle man between your client and the actual IRC server you’re connecting to. Message storing/history is one of the advantages a bouncer usually offers. Another one is the ability to connect to multiple IRC servers at once and deal with things like reconnects (due to network outages for example) and other annoyances on its own.

Especially when used over the Onion network, these things can become tiring to deal with in the client. Similarly, when you connect using a phone and you’re trying to chat over a somewhat unstable mobile connection, you (and the folks idling in the same channels as you) will highly appreciate the stability of your IRC user on the network, while your bouncer will take care to deal with your phone client continuously reconnecting.

There’s also a privacy and security aspect to this. Usually, when you connect to an IRC server directly, using a client on your PC or your phone, your source IP/hostname is visible to at least the IRC server – and often also to the other users on that IRC server. You might not want for others to know the country/location you’re connecting from, for example. You could use a VPN to hide that, however, VPNs can be restricted on IRC servers, requiring at least an already registered account – sometimes of specific age – and identification through SASL. Also, as with Tor, connection stability might nevertheless become an issue.


I’m assuming you’ve gone through the IRC server post, so I won’t be repeating the basic setup of an OpenBSD cloud instance. We will be using a similar setup here: A Vultr OpenBSD Cloud Compute instance – I’ll use their AMD High Performance 1vCPU/1GB instance for this, as it is more than enough for running our bouncer, ZNC, as well as the Tor client that we need to connect to the Onion network.

PS: The comments on the hackernews post also contained a couple good recommendations and further reading material like man afterboot. Thanks to the person that initially shared my post, as well as to everyone commenting and providing further info!


Let’s start by installing the packages we’ll be needing and afterwards creating a dedicating user account for ZNC:

znc# pkg_add znc tor proxychains-ng supervisor
znc# groupadd _znc
znc# useradd -d /home/znc -m -c "ZNC" -g _znc -L daemon -s /sbin/nologin _znc

Before we go ahead and configure ZNC, we need to get an SSL certificate for securing the connection from our IRC client(s) to ZNC. For that we’ll have to set up a (sub)domain that points to the IPv4 (and ideally IPv6) address of our cloud instance. While it is possible to purchase SSL certificates issued for IP addresses, Let’s Encrypt (the free SSL service we’ll be using) will not support this.

The following step is optional, as in, if you don’t have a domain available on which you could set up a subdomain that’ll point to the cloud instance, you could use a self-signed certificate and either import it on your IRC clients’ systems and trust it, or disable SSL verification in the clients’ configurations. I would not recommend doing any of that, though.

To get an SSL certificate (from Let’s Encrypt) we’ll use OpenBSD’s acme-client and set up its configuration, as well as a cron job that will make sure our certificate gets automatically prolonged.

First, create the file /etc/acme-client.conf with the following content:

authority letsencrypt {
  api url ""
  account key "/etc/ssl/private/letsencrypt.key"
domain {
  domain key "/etc/ssl/private/"
  domain certificate "/etc/ssl/"
  domain full chain certificate "/etc/ssl/"
  sign with letsencrypt

Make sure to replace with the (sub)domain you pointed to your cloud instance. Next, we’ll need to configure httpd to run on port 80 and provide access to the ACME challenge. Create the file /etc/httpd.conf with the following content:

server "" {
  listen on * port 80
  root "/htdocs/"
  location "/.well-known/acme-challenge/*" {
    root "/acme"
    request strip 2

Additionally create the folder /var/www/htdocs/ and place an empty index.html into it.

Let’s enable httpd:

znc# rcctl enable httpd
znc# rcctl restart httpd

We can now try to issue our SSL certificate by running the following command:

znc# acme-client -v

Your newly issued SSL certificate should be available under /etc/ssl/{crt,pem} and /etc/ssl/private/ Let’s put the SSL topic to the side for a moment and continue with ZNC.

Switch to into the _znc user account by issuing the following command:

znc# su -s /bin/sh -l _znc -

First, run the freshly installed znc binary with the --makeconf parameter to create a basic configuration under ~/.znc/:

znc$ znc --makeconf

If you have skipped the SSL part, you could use znc --makepem to issue a self-signed certificate now.

With the basic configuration in place, let’s adjust the config file under ~/.znc/configs/znc.conf. I’m going to demonstrate a config containing a single user whose password was generated using the znc --makepass command and who will connect to only the OFTC IRC network via their Tor Hidden Service. You’re free to adjust all settings to your needs and add further networks. The ZNC bouncer in this example will listen on port 1337, but you’re free to change that.

AnonIPLimit = 10
AuthOnlyViaModule = false
ConfigWriteDelay = 0
ConnectDelay = 5
HideVersion = true
MaxBufferSize = 500
ProtectWebSessions = true
SSLCertFile = /home/_znc/.znc/cert1.pem
SSLDHParamFile = /home/_znc/.znc/cert1.pem
SSLKeyFile = /home/_znc/.znc/privkey1.pem
ServerThrottle = 30
Version = 1.8.2

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

<User ahab>
        Admin = true
        RealName = Captain Ahab
        Nick = ahab
        AltNick = captain
        Ident = ahab
        AppendTimestamp = false
        AuthOnlyViaModule = false
        AutoClearChanBuffer = true
        AutoClearQueryBuffer = true
        ChanBufferSize = 5000
        DenyLoadMod = false
        DenySetBindHost = false
        JoinTries = 10
        LoadModule = chansaver
        LoadModule = controlpanel
        MaxJoins = 0
        MaxNetworks = 5
        MaxQueryBuffers = 50
        MultiClients = true
        NoTrafficTimeout = 180
        PrependTimestamp = false
        QueryBufferSize = 50
        QuitMsg = All my means are sane, my motive and my object mad
        StatusPrefix = *
        TimestampFormat = [%H:%M:%S]
        Timezone = America/New_York

        <Network oftc>
                FloodBurst = 4
                FloodRate = 1.00
                IRCConnectEnabled = true
                JoinDelay = 0
                LoadModule = simple_away
                LoadModule = nickserv
                LoadModule = perform
                LoadModule = sasl
                Server = oftcnet6xg6roj6d7id4y4cu6dchysacqj2ldgea73qzdagufflqxrid.onion +6697
                TrustAllCerts = false
                TrustPKI = true
                TrustedServerFingerprint = be:1c:d4:42:da:36:46:8a:6a:0c:62:a8:d1:c7:c5:18:01:d5:06:65:56:46:2a:11:5c:c2:50:55:b2:d9:db:a2

                <Chan #openbsd>

                <Chan #tor>

        <Pass password>
                Method = sha256
                Hash = 19365170747edd9d587b815f58cf0a590626ff98757d2af074cc8bc60d2a47c8
                Salt = O)jP.vgSuq4Ek;m1xXWG

Next, let’s go back to the SSL topic. Log out of the _znc account (Ctrl+d) and (as root) run crontab -e. This will open the current crontab for editing. Add the following line at the very end of the file:

0       0       *       *       *       acme-client -v && cp /etc/ssl/ /home/_znc/.znc/cert1.pem && cp /etc/ssl/private/ /home/_znc/.znc/privkey1.pem && chown _znc._znc /home/_znc/.znc/*.pem

This will make sure our SSL certificate won’t expire. In addition, it will copy newly issued SSL certificates to the place that ZNC looks for its SSL certificates. Run the cp commands manually once to copy the currently issued certificate.

“Do I need to restart ZNC every time this runs in order for it to reload the newly copied SSL certificate?” you might ask. The ZNC wiki says:

“To apply this new certificate file to ZNC, just put (replace) it in ZNC’s work folder. As of 1.6.2, ZNC will reload znc.pem each time a client connects, but it’ll be fixed in future.”

Now let’s set up supervisor. Go ahead and create a new file in /etc/supervisord.d/ named znc.ini and add the following content:

command=proxychains4 znc -f

As you can see, we’re not launching znc directly but instead wrap it with proxychains4, which forces it through a proxy connection. That connection can be configured in /etc/proxychains.conf and should by default contain the following line:

socks4 9050

If you can spot it, no need to change anything. If not, simply add it yourself. Last but not least you’re free to configure /etc/tor/torrc the way it suits you best. The default config is fairly sufficient and will work for this demo though. If you prefer using regular hostnames for configuring your IRC connections, you can use MapAddress to map the regular hostname/domain to a Tor address, e.g.

MapAddress oftcnet6xg6roj6d7id4y4cu6dchysacqj2ldgea73qzdagufflqxrid.onion

Now let’s go ahead and enable/start all services:

znc# rcctl enable tor
znc# rcctl restart tor
znc# rcctl enable supervisord
znc# rcctl restart supervisord

You can check whether znc started up successfully by monitoring the supervisor log:

znc# tail -f /var/log/supervisord.log

Tor will output logging info in messages:

znc# tail -f /var/log/messages

If znc won’t start and you want to know why, you can redirect its output to the log. Check the supervisor manual for more info. You could also log in as _znc user (the su command from before) and run proxychains4 znc -f manually. Keep in mind to stop supervisor before doing so.

Assuming everything started successfully and you didn’t change the port in the znc config, znc should not be running on 1337:

znc# netstat -an | grep "1337"


We can now connect to it. I’m going to use my preferred IRC client, irssi, but you’re free to pick whichever client you’d like. Configuration will likely be similar for most clients.

On our client machine, let’s edit the irssi configuration under ~/.irssi/config. I’m only going to show the relevant parts and won’t include aliases, statusbar configuration or other things that you’d likely want to configure for irssi:

servers = (
    address = "";
    chatnet = "oftc";
    port = "1337";
    use_ssl = "yes";
    ssl_verify = "yes";
    autoconnect = "yes";
    password = "ahab/oftc:IT'S_THE_WHALE!";

chatnets = {
  oftc = { type = "IRC"; };

channels = ( );

Most things should be self-explanatory. password however is a special case here. It is a combination of three things:

<znc user>/<network>:<znc password>

Find more info on this here and here. If you have configured multiple networks on ZNC, simply add additional servers entries to the irssi config, along with additional chatnets assignments. Every entry will basically look the same, with the only difference being the <network> part. If you decide to use different ZNC users for different networks, you’re able to by changing the <user>/<password> parts as well.

Launching irssi will make it connect to your ZNC bouncer instead of directly to the IRC network itself. It could be, that ZNC was unable to establish the connection to OFTC in our example, as the trusted fingerprint of the OFTC server might have changed. In that case, ZNC will tell you how to accept the new fingerprint, so read the *status output carefully and follow the instructions.

Speaking about *status, you can easily talk to your ZNC bouncer by messaging *status: /msg *status help

There, you are able to issue administrative commands to ZNC. SaveConfig is an important one if you decide to change settings using the *status interface.

If you’re looking for a solid Android client to connect to your newly set up ZNC bouncer, check out Revolution IRC. It’s really great and its configuration is similar to the one demonstrated here.

BTW: Did you know that you could get push notifications for mentions and private messages on your phone and other devices? Check out znc-push!

IRC and Tor

While there are still networks with solid principles and values, like OFTC, that don’t shy away from putting up with the spam that comes with allowing unauthenticated Tor connections, most popular networks however don’t seem to care enough about people’s privacy to put up with the administrative efforts that are required for running a Tor Hidden Service in first place or allow connections without previous clearnet registration. The Tor project maintains a somewhat up to date list of networks that provide access via Tor.

In regard of hosting, the Tor project also maintains a list of good and bad ISPs in case you’d rather want to go with a different cloud instance provider.

If you decide that you’d rather not connect from your ZNC installation via the Onion network, you can rcctl disable tor as well as remove the proxychains4 wrapper from the supervisor config. ZNC will then use a direct connection to every server. I would however suggest avoiding networks that actively prohibit access via Tor or limit it to existing users (via clearnet registration) that authenticate using SASL.

Happy idling everyone!

Enjoyed this? Support me via Monero, Bitcoin, Lightning, or Ethereum!  More info.

collections [ ] · tags [ ]
published [ ] · updated [ ]