Secure private IRC server with Tor support

Kalevi Kolttonen <>
Unto Sten <>
Helsinki, Finland
July 2018

October 2018: This document may still have some educational value, but it has been superceded by a newer article Building a secure chat infrastructure. It describes Untochat, a chat infrastructure that aims to be both secure and anonymous.


In this tutorial we will show you how to set up a secure private IRC server with Tor support. The server will be suitable for private use only, and it is for instance intended to help oppressed groups of individuals who need to communicate with each other without fear of being monitored. To reiterate, the secure private IRC server described here cannot be a member of any public IRC network - quite on the contrary, it must be totally isolated on one trusted host and it must never speak to other IRC servers.

Some Internet users know that IRC is an old but still a pretty functional Internet chat protocol. It was developed all the way back in 1988 by Jarkko Oikarinen, a Finnish computer scientist and programmer, so as of this writing, IRC protocol is already about 30 years old.

One of the weaknesses of the IRC protocol is that it contains no way to specify character sets for IRC messages or IRC channels. Despite that, it all kind of works and there are still public IRC networks that attract e.g. technically oriented computer users and hardcore hackers. There are people who have been on IRC for literally decades, and they see no reason to give up their habit.

Another well-known weakness is that the original IRC protocol consists of unencrypted plain text data flow. This naturally means that evil eavesdroppers who are in the position to monitor Internet traffic can see other people's IRC conversations. But nowadays some IRC server implementations do support TLS for server authentication and IRC network traffic encryption. Despite that, in this tutorial we will stick with the simple plain text IRC protocol. The reasons for that decision will be explained next.

Why did we choose to demonstrate setting up an IRC server instead of, say, a more modern XMPP/Jabber based Internet chat server? Or an IRC server with TLS enabled? One extremely central reason is simplicity. Setting up a TLS enabled XMPP or IRC server requires having a certificate and a matching private key. XMPP servers also often need to be configured to authenticate clients. They may also require configuring database servers such as MariaDB. We grant that it is not very difficult if you know what you are doing. However, in contrast to that, Next Generation IRC Daemon is much easier to set up even for the pretty inexperienced Unix/Linux users.

It is our goal to help e.g. human rights activists to build a trusted chatting infrastructure that should be difficult for others to monitor and spy on. Those activists may well lack technical skills so things should be made very accessible for them and everyone. In addition, we believe it is in general a pretty good idea to avoid complexity whenever we can. Remember that the famous physicist and philosopher Albert Einstein once said:

Things should be made as simple as possible - but not any simpler.

Our secure private IRC server model places some demands on the users, that is, the regular clients who wish to use the IRC server for chatting and being safe. It turns out that the users only need an SSH client and Tor Network access. They need not worry about installing or updating IRC client programs at all, because the IRC server administrator provides them with a centrally administered IRC client. The downside is that the client proposed here (called irssi) is terminal-based, i.e. it does not have a flashy and modern GUI that some users might expect. We may be overoptimistic, but we do believe that almost anyone can learn to use irssi with little practice.

Security-wise, the infrastructure suggested here has one major issue: We allow shell access to the IRC server. This means that malicious users could gain control of the server by applying yet unknown local root exploits. To combat that, we could sandbox and limit the users as much as we can with e.g. Linux security mechanisms such as SELinux. But we have chosen not to do that, because it is our initial assumption that we are here building a strictly private IRC server. That means that we should have only trusted users and no suspicious strangers.

In general, it is evident that allowing shell access to a server in order to access its services is a very bad idea indeed. We humbly suggest the model presented here first because it is a relatively simple solution, and second because the whole concept of having a private IRC server is based on having a group of mutually trusted users. In other words, if you have untrusted users, then this model is not suitable for you.

It seems to us that one considerable advantage of this model could be that it allows clients to easily use operating systems such as Tails. Tails is a special-purpose OS based on Debian Linux. It can be booted using an USB stick and it leaves no traces anywhere on the computer it runs on. All Tails network traffic, including DNS queries, is routed to the Tor Network, providing users good chances of being anonymous. Using Tails, users always have an SSH client and they could contact the secure private IRC server easily without the need to configure IRC clients by themselves. Tor is also automatically available for them.

Using a centrally administered irssi on the IRC server also eliminates the possibility of someone's IRC client being misconfigured to log conversations on disk. By default irssi logs no conversations.

System overview

Let's get started. First of all, our secure private IRC server will never accept direct connections from the Internet. The IRC daemon ngircd is bound to localhost (IPv4 only. The origin of this basic idea dates back several years, and we are sure neither of us is the original inventor. We thus make absolutely no claims about originality or invention here. We just wish to spread some privacy related information to those who might benefit from it.

Nevertheless, one of the authors of this article used to run a carefully firewalled OpenBSD operating system on a Sun Ultra 80 Unix workstation working as a trusted Unix server. The Sun Ultra 80 was located in a server room the location of which the author is not willing to discuss, but suffice it to say that the machine ran almost uninterrupted for some years. During that time period, it served many happy IRC chat users who were afraid of surveillance or even persecution.

Older IRC daemon called ircd was bound to localhost and all the IRC users had to first login to the Sun Ultra 80 server using Secure Shell (ssh). After that, they used Irssi (irssi) IRC client program to connect to the locally running ircd. The possible outside observers saw only SSH connections, but unless they did network traffic analysis, they could not figure out the SSH connections were used to ultimately use IRC chatting contained inside the destination host. So in our model the basic principle for setting up a secure private IRC server is to take care of two essential things:

  1. IRC daemon must always bind to localhost (IPv4 only. This prevents all IRC connections from the Internet and hides the fact that our host is even running IRC daemon.
  2. IRC daemon must never contact other IRC daemons located on the Internet. This prevents chat and IRC server information from leaking out.

In our case, both of the requirements above can be achieved by editing the IRC daemon's configuration file /etc/ngircd.conf. For additional security and to guard against human errors, it might be good to use firewall rules (iptables on Linux) to block both incoming and outgoing TCP connections to the usual IRC ports (i.e. 6666-6668). However, we leave firewall configuration up to the reader.

Assume that Alice and Bob want to secretly chat with each other. Then look at the picture below, starting from the left side.

UPDATE July 30th, 2018: WARNING! Unfortunately our original overview picture below is inaccurate and potentially misleading!

Please accept our apologies for messing up the picture. We have left the original here, but the revised overview picture is right below.

Inaccurate and misleading original overview
secure irc server with tor support

Revised overview
revised secure irc with tor support

The required steps for Alice are pretty straightforward:

  1. Alice uses torified ssh client program to login to the SSH server via anonymizing Tor Network. For reasons of anonymity, our SSH server offers SSH login access via Tor Onion Services only. It is crucial to understand that the SSH server is the very same host that runs the Next Generation IRC daemon inside it, bound to localhost. Review the picture if you are unsure. For simplicity, you can ignore the Tor aspects for now. We will explain the related Tor concepts later in this article.
  2. Once Alice is logged in (either using her SSH public key or personal password for SSH authentication), she will connect to the local private IRC daemon using irssi IRC client program. Alice is now ready to join IRC channels. She can also send and receive IRC private messages just like when using a normal, public IRC server. However, this time the IRC protocol is totally contained inside the trusted host, so no outsiders can monitor it unless they:
  1. break Tor and SSH protocol's strong encryption
  2. break into Alice's computer
  3. break into Bob's computer
  4. break into the SSH server host where our IRC daemon is running

What about Bob? His steps are exactly identical. Both Alice and Bob must of course have Unix/Linux shell accounts and home directories on the SSH/IRC server. Otherwise they cannot do SSH logins to later access the private IRC server.

It is perhaps worth pointing out explicitly that in this secure chat model, the data flow between IRC client program irssi and the IRC server ngircd is totally in plaintext. Why is this so? It does not have to be encrypted, because the IRC protocol runs strictly inside the host machine. SSH protocol encrypts the network traffic between Alice/Bob's personal computer and the SSH server host. In addition to that, the SSH protocol is contained within Tor Network cells (i.e. data packets used in Tor protocol). The Tor cells are also encrypted! That is why using IRC like this should be quite safe.

Putting the theory into practice

We used Fedora Linux 28 with Xfce desktop on a QEMU virtual machine for putting the theory into practice. With some effort and creativity, we hope you can adopt the steps described here to other Linux (e.g. Debian GNU/Linux, Ubuntu) and Unix systems such as FreeBSD, NetBSD and OpenBSD. We wanted to stick with Fedora Linux, because we are already familiar with it. It is just a matter of personal preference. We claim no superiority over other systems out there.

Setting up IRC client, IRC server, SSH server and Tor

Before we continue, we need to deviate a little bit from the main topic. To save our sanity, it is our decision to always install Xfce desktop as the very first thing after Fedora Linux installation. It is up to you if you want to follow us. If you do, find a way to open a terminal (as there are no sensible menus available in the weird default Gnome desktop, we just type "terminal" into the search box) and once you have the shell, then type:

sudo dnf -y groupinstall "Xfce desktop"

Logging out might well suffice, but to be sure we reboot the machine, wait for the GDM login prompt, and type in the login name. We click the wheel symbol, select "Xfce session" and enter the password. When logged in to Xfce for the first time, we choose the default, and proceed from there according to our taste. For us, the default desktop on Fedora Linux 28 is unfortunately way too confusing and clumsy to be usable at all. But maybe some people like it.

Okay, enough of that digression. We next install Next Generation IRC daemon ngircd, a terminal-based (it uses ncurses library) IRC client irssi and The Onion Router tor:

sudo dnf -y install ngircd irssi tor

IRC client irssi

/etc/skel/ directory is the "skeleton", i.e. the basis, or blueprint, for creating home directory content for new Unix/Linux users.

For the convenience of our future users, we will add a shell alias so that all new users can start irssi with just typing a simple command i. So we edit /etc/skel/.bashrc by adding one line there:

alias i='irssi -c -p 6667'

Then we create a test user alice:

sudo adduser alice

We assign a safe password for alice:

sudo passwd alice

Then (using our own password, not the one we just invented for alice), we switch to alice user:

sudo -i -u alice

Being now alice user, we verify that the i shell alias is effective:

which i

We should see a reply resembling the following:

alias i='irssi -c -p 6667'

To exit alice user's shell session, we issue command:


That is all there is. We are now done with irssi configuration. When a user first starts irssi, it will create directory ~/.irssi for the user. The configuration file will be automatically written there, and it should be fine as it is. We verified that irssi does not log IRC conversations anywhere by default, and that is exactly what we want.

IRC server ngircd

We start by editing /etc/ngircd.motd. The abbreviation "motd" stands for "message of the day". It is just a brief greeting message to be displayed when users connect to our IRC server. We use vim text editor:

sudo vim /etc/ngirc.motd

Adopting the habit of never wanting to give away any unneeded information, we simply insert a boring text "This is a MOTD" and save the file. Please be aware that Vim can be very confusing and difficult for beginners, so you could use a simpler text editor such as nano, or whatever you are confident to use.

Now comes the more crucial part. To configure our IRC server, we must edit /etc/ngircd.conf. Again, feel perfectly free to use your favourite text editor. We do still recommend that you would learn vim if you plan to do any serious system administration in the long run. It will pay off. We have learned to love vim and fire it up:

sudo vim /etc/ngirc.conf

For clarity and extra information, we include some comments taken from the configuration file. If you use a later version of ngircd.conf, the comments could be slightly different. All comments here start with "#" and they are meant for human administrator's use only. The IRC server itself ignores comments when parsing the configuration.

We define server name:

# Server name in the IRC network, must contain at least one dot
# (".") and be unique in the IRC network. Required!
Name = localhost.localdomain

We add some dummy information so that our IRC daemon will not complain in the system logs during its startup:

# Information about the server and the administrator, used by the
# ADMIN command. Not required by server but by RFC!
AdminInfo1 = Description
AdminInfo2 = Location
AdminEMail = dummy@localhost.localdomain

This is very important. We now command our IRC daemon to bind to localhost (IPv4 only. This prevents access from outside world:

# Comma separated list of IP addresses on which the server should
# listen. Default values are:
# "" or (if compiled with IPv6 support) "::,"
# so the server listens on all IP addresses of the system by default.
Listen =

We allow our users to join as many channels as they like:

# Maximum number of channels a user can be member of (0: no limit):
MaxJoins = 0

This might not be that important, since all our IRC clients come from the localhost. But we set it anyway:

# Set this hostname for every client instead of the real one.
# Use %x to add the hashed value of the original hostname.
CloakHost = localhost.localdomain

The following setting overrides the Real Name field with IRC nickname. Again, this is probably not at all important, but we set it to "yes":

# Set every clients' user name to their nickname
CloakUserToNick = yes

But these settings are extremely important. We absolutely never want our private IRC server to connect to other IRC servers. Make sure to set these to "no":

# Try to connect to other IRC servers using IPv4 and IPv6, if possible.
ConnectIPv6 = no
ConnectIPv4 = no

All our IRC clients always come from localhost, so this setting probably does not matter much. But it makes perfect sense to turn it off to avoid unnecessary DNS lookups. So we set DNS to "no":

# Do DNS lookups when a client connects to the server.
DNS = no

Doing IDENT lookups makes no sense in our case. Using IDENT queries, an IRC server will contact a client to verify Unix/Linux username. We do not run IDENT daemon and we know that all our IRC users come from inside our own host. Without hesitation, we turn Ident queries off:

# Do IDENT lookups if ngIRCd has been compiled with support for it.
# Users identified using IDENT are registered without the "~" character
# prepended to their user name.
Ident = no

MorePrivacy sounds promising, but probably is not that crucial in our case. But we turn it on:

# Enhance user privacy slightly (useful for IRC server on TOR or I2P)
# by censoring some information like idle time, logon time, etc.
MorePrivacy = yes

PAM is a widely used Pluggable Authentication Module framework originally invented by Sun Microsystems, a Unix vendor responsible for Solaris (formerly "SunOS") UNIX Operating System. By the way, Sun Microsystems also created SPARC RISC CPUs. Oracle bought Sun Microsystems many years ago.

In any case, let's get back to business. We do not want PAM authentication here. Why not? Because we know that all our IRC users have already been authenticated during the SSH login phase, so we allow connecting to the IRC daemon with no passwords required. We turn PAM off:

# Use PAM if ngIRCd has been compiled with support for it.
# Users identified using PAM are registered without the "~" character
# prepended to their user name.
PAM = no

Finally we wish to allow unlimitd amount of connections from the same client IP, because all our clients come from as far as the IRC server sees it:

# Maximum number of simultaneous connections from a single IP address
# the server will accept (0: unlimited):
MaxConnectionsIP = 0

In case you are wondering why we did not define IRC port anywhere, the explanation is simple. TCP port 6667 is the default in ngircd distributed with Fedora Linux 28, so we were able to skip over that setting. Our IRC server configuration is now hopefully complete. We save the file and exit the text editor.

Before starting ngircd, we want to check that we made no mistakes while editing. We let ngircd verify its configuration file sanity:

sudo ngircd --configtest

If you see warnings or errors, then go back and edit the configuration file until they are fixed.

It is now time to start ngircd. We issue command:

sudo systemctl start ngircd

If all went well, our secure private IRC daemon should be up and running. We verify that it is:

sudo systemctl status ngircd

If you see errors, try to find a way to fix them. Inspecting system logs often proves helpful.

Before connecting to ngircd, we are paranoid and wish to make sure it is indeed listening localhost only. The classic Unix/Linux command netstat is deprecated on Linux, so we try to avoid it, and use the new replacement tool called ss instead. We give command:

ss -n -l | grep ':6667'

We can be happy because we see the text that we expected:

It indicates that port 6667 is in the listening state and we can be pretty sure that it is ngircd that is bound to localhost (IPv4 just like we wanted. You could verify that using the PID of ngircd, but we did not bother to do that.

To finish our ngircd testing, we temporarily become our test user alice again:

sudo -i -u alice

Being now alice, we launch irssi IRC client program using the previously created shell alias i:


We should see how irssi successfully connects to the local IRC server. If it worked out, congratulations! Your secure private IRC server is now up and running.

To exit irssi, we just type a familiar IRC command:


Then we quit alice user's shell session like usual:


There is one final step to be done with respect to our IRC server. We permanently enable ngircd so that the IRC daemon will always automatically start up when we boot our machine. It is of course up to you whether you want to do this or not. In case you want to enable ngircd too, then tell that to systemd by typing command:

sudo systemctl enable ngircd

SSH server sshd

In this section we configure sshd daemon. It will then allow users to login using encrypted SSH protocol.

sudo vim /etc/ssh/sshd_config

We find ListenAddress setting and edit it to be:


Now if you have previous experience with running traditional Unix/Linux network servers, using ListenAddress for an SSH server will probably feel quite weird. After all, our goal here is to allow SSH logins from the outside world, so why on earth did we bind sshd to the localhost only (IPv4 Do not worry about this. We will configure a Tor Onion Service later in this tutorial, and in that section this peculiar sounding issue will be explained. So let's move on.

Next we find PermitRootLogin and disable it:

PermitRootLogin no

To restrict SSH access to Unix/Linux group ircusers, we add the following setting:

AllowGroups ircusers

We start sshd:

sudo systemctl start sshd

After that we verify that it got up and running:

sudo systemctl status sshd

Then we use ss utility again to verify that TCP port 22 is in listening mode:

ss -n -l | grep ':22'

If you see text "", you should be fine.

We do a test login using ssh as user alice:

ssh alice@localhost

When asked "Are you sure you want to continue connecting (yes/no)?", we answer "yes". When prompted, we enter alice's password.

Even though her password is correct, we should receive "Permission denied, please try again.". This is to be expected because alice is not yet a member of the group ircusers. So first we create that group:

sudo groupadd ircusers

And then we add alice to the group:

sudo usermod -a -G ircusers alice

We try to SSH login again as alice:

ssh alice@localhost

This time we do get logged in. To quit alice's shell session, we just type:


Finally we tell systemd to always automatically bring up sshd when booting our machine. It is up to you whether you want to follow us. If you choose to do so, just type:

sudo systemctl enable sshd

The Onion Router Tor

On Fedora Linux 28 Security-Enhanced_Linux (SELinux for short) is enabled by default. We verify that fact by issuing command:

sudo sestatus

We should get a response like the following:

SELinux status: enabled
SELinuxfs mount: /sys/fs/selinux
SELinux root directory: /etc/selinux
Loaded policy name: targeted
Current mode: enforcing
Mode from config file: enforcing
Policy MLS status: enabled
Policy deny_unknown status: allowed
Memory protection checking: actual (secure)
Max kernel policy version: 31

So we see that SELinux status is enabled, mode is enforcing, and finally the loaded security policy is targeted. That is all fine, but we must tweak a Tor Onion Service related SELinux boolean variable called tor_can_onion_services. To see why this is so, we query the status of that variable:

sudo getsebool tor_can_onion_services

The response we get is:

tor_can_onion_services --> off

You probably guessed this means that by default SELinux prevents tor daemon from creating Tor Onion Services. So we tell SELinux to drop that restriction:

sudo setsebool -P tor_can_onion_services on

After that we proceed to edit /etc/tor/torrc to configure tor daemon. Again, you can use the text editor you want, but we keep on using vim:

sudo vim /etc/tor/torrc

Having the file open for editing, we locate the section that deals with Hidden Services. "Hidden Services" is a deprecated name for Onion Services, but for backward compatibility reasons it still survives in the configuration setting names.

Fortunately Tor developers have made things easy for us. All we need to do is add two simple lines:

HiddenServiceDir /var/lib/tor/hidden_service
HiddenServicePort 22

Those alone are sufficient to define the Tor Onion Service we want to create.

Notice that the second line HiddenServicePort 22 maps Tor Onion Service port 22 to our sshd daemon's listening port When offering Onion Services, Tor works in a pretty counter-intuitive way. Instead of listening for incoming connections via a TCP socket, tor daemon makes a permanent outbound connection to the Tor Network! Using that TCP connection, tor makes our Onion Services available to Tor Network users! That is indeed clever.

The exact details of this surprising arrangement are pretty complicated to understand and require studying Tor architecture. However, once we accept the fact that we need no public listening TCP sockets, we soon arrive at a very, very important realization: We can easily run our own Tor Onion Services from home, even if our machine is in a private network masked behind a home router that does NAT. On such a computer, we can even run our Tor Onion Services inside isolated virtual machines, because all we need is working outbound TCP connections to the Tor Network! This is just amazing and extremely convenient.

So we tell systemd to launch tor daemon:

sudo systemctl start tor

We then wait for a short while, and hoping for the best, proceed to query tor daemon's status:

sudo systemctl status tor

We see that is all is well, but how do we figure out how to access our new Tor Onion Service? We have provided no name for the service, but that is perfectly fine. We cannot decide the hostname. Instead Tor works its magic behind the scenes and comes up with a unique hostname for us. Using cat we can see it:

sudo cat /var/lib/tor/hidden_service/hostname

We should see the Tor Onion Service hostname as something like:


Note that $hash will contain stuff that looks like gibberish to many, but rest assured that it is meaningful in the Tor Network context.

Finally we verify that the newly created Tor Onion Service is indeed available via Tor Network. To do that, we use our test user alice to make a "torified" (i.e. Tor enabled) SSH connection to our hidden SSH server. We type:

torsocks ssh alice@$hash.onion

Make sure you remember to replace $hash.onion with whatever your file /var/lib/tor/hidden_service/hostname contains!

If we did everything right, we should get logged in. However, the interactive SSH connection may feel a bit slower than usual because the network traffic is routed via Tor Network just like we wanted.

Last but not least, we chose not to enable automatic tor daemon startup. But if you wish to do so, instruct systemd like this:

sudo systemctl enable tor


This tutorial showed you one possible way of setting up a secure private IRC server. Such a server could be very useful for e.g. activist groups that have mutual trust between their individuals. In practice, this probably implies having a rather small user base.

Our model is easy from the chat user's perspective: The server administrator manages both the IRC server and the IRC client, so regular users need only an SSH client and access to Tor Network. This means that a (nearly) zero-configuration special-purpose security-focused OS such as Tails could be very suitable for chat users. The users could run Tails live system using USB sticks, but any system (e.g. OSX, Windows, BSD, regular Linux) with an SSH client and Tor Network access would be sufficient.

If you have untrusted users, the model presented here is not recommended, because allowing shell access to the shared server could prove catastrophic. Modern operating systems are extremely complicated, with many moving parts, and this implies that users with local access have extra attack vectors compared to the users who are not able to login.

We wish you good luck.