Obligatory Warning
Building a server isn’t like getting a prickly pear cactus, bamboo plant, or even like the Ronco® Showtime® Rotisserie & BBQ. This is like getting a dog: you have to give it care and attention or it will end up becoming a menace to society.
If you build this server and neither update it nor check up on it, it will almost guaranteed become part of a botnet. Depending on your host and jurisdictions, you may also end up being liable for any damage (both real and intangible) that this server causes. Which is why I provide Web Administration services, but I digress.
You have been warned, and I’ll sleep soundly arming you with the following knowledge.
Background
Communication is expanding rapidly in this day and age. We have texting, e-mail, Slack, Discord, Skype, FaceTime, Facebook Messenger, LINE, and the list goes on. Geeks (and, by association, the FOSS community) tend to embrace counterculture, which is why they thoroughly adopt IRC. For a more detailed explanation, check out this opinion piece: “Please don’t use Slack for FOSS projects.”
I’ll not bore you with the details of IRC (instead, I defer to Wikipedia). Just know that it has been around since 1988 and as of April 2011 has more than 500,000 users with nearly as many channels. As mentioned in the opinion piece, a new specification is even in the works.
Since social norms demand that I also embrace IRC, I set out to make my own IRC server.
Abstract
I know, in my CentOS 7 AMI Web and Mail Server with DDNS article, I say that I prefer CentOS for Internet services. ircd-hybrid is an IRC server that is easy to configure, but it’s neither available in the CentOS default repos nor EPEL. That means we’d have to manually download and compile from source then reconfigure it every single time there’s an update. Nope. Not doing it (for free, anyway).
So, now what? Go back to using FaceTime? Not quite yet. There are a few distros with ircd-hybrid in their repos. The only ones I favor are Debian and Ubuntu. Now, I like Ubuntu as a development environment (as I mention in my clashcallerbot-reddit Ubuntu Setup), but I have concerns about Ubuntu in production. Ubuntu’s weakness is that it supports too many packages, each with potential security holes that can arise randomly.
By contrast, Debian is a rock. In fact, Debian’s weakness is that it is too stable. Debian went from Python 3.5 to Python 3.7 between Debian 9 (stretch) and Debian 10 (buster) - deferring many Python features that would be appreciated for a distribution upgrade. Meaning that the only way to get Python 3.6 is to compile from source. For development, this is a deal-breaker, but for running an IRC server, this is perfect.
As I explain in my CentOS 7 AMI Web and Mail Server with DDNS article, an AMI on AWS is the way to go when it comes to running servers. Debian provides a limited set of official Debian AMIs, and no offense to Debian 8 (jessie), but I’m going with stretch.
In addition to ircd-hybrid, we’ll also install ZNC as an IRC Bouncer since it is included in the Debian repos and has nifty features like channel buffering.
The most complete IRC server guide I found was written in 2012 using an Ubuntu Server 12.04.1 LTS AMI, so this guide will fill the Debian 9 AMI IRC server with IRC Bouncer gap to some degree.
Materials
Pretty sparse: an AWS EC2 instance running Debian 9 AMI.
Simple enough, right?
Procedure
Setting up this system is easy. Just get set up to use Amazon EC2. Now, the fun begins.
Configure EC2
First, we need a VPC. The default VPC can be used if this is the first EC2 instance, but try not to have everything running in the same subnet if they don’t need to cross communicate.
Tip
- Enabled Auto-assign IPv4 for the subnet since it will only have one network interface.
Next, it would be wise to have a custom IAM role for the AWS service.
Now that our instance can connect to the Internet and can’t run rampant across AWS services, we can get started with an EC2 instance, just skip Step 3 because we don’t want to delete it when we’re done.
Tip
- When selecting an AMI to use, choose the official Debian 9 AMI.
- Opted for a t3.nano instance since that’s what’s ‘in’ right now and this will only be an IRC server.
- Used Security Groups to limit SSH access and port 6664 to My IP, then allow 6667 and IRCS port 6697 (optional) from Anywhere. However, restrict 6667 and 6697 to My IP as you work on the services. After they’ve been configured, they can be set to accept Inbound connections from Anywhere.
- Used a key pair for SSH access.
- It is a good idea to enable billing alerts in case it gets out of hand.
- The root volume is deleted on termination of the instance, so I enabled termination protection.
- Made the root volume 5 GB because it should be fairly low volume since I’m not popular.
- Went with magnetic storage because it costs less than SSD storage and the I/O speed is not needed.
Configure Debian 9
Now that Debian 9 is installed, go ahead and run a sudo apt update && sudo apt upgrade to get everything updated.
Tip
- When connecting from Windows operating systems, I prefer PuTTY/KiTTY, but there is a doc detailing setup.
- It’s also a good idea to reboot the instance in case any kernel updates were applied.
The Debian 9 AMI already has SSH set up and external PAM authentication disabled, but automatic upgrades would be a handy addition.
The package unattended-upgrades is already installed, so we only need to modify the file at /etc/apt/apt.conf.d/50unattended-upgrades by invoking vim (vim FTW!):
sudo vim /etc/apt/apt.conf.d/50unattended-upgrades
Type a for “append,” then change or add the following:
Unattended-Upgrade::Origins-Pattern {
"o=Debian,a=stable";
};
Unattended-Upgrade::Remove-Unused-Dependencies "true";
To save, press the ESC key to enter command mode, type : to enter a command, then wq for “write and quit,” and press the ENTER key to issue the command. If you don’t want to save changes, replace wq with q!. Bam, you’re a vim wizard, Harry.
Tip
To confirm the settings, run sudo unattended-upgrades -d.
There should already be a file at /etc/apt/apt.conf.d/20auto-upgrades with the following lines:
APT::Periodic::Update-Package-Lists "1"; APT::Periodic::Unattended-Upgrade "1";
Let’s confirm unattended-upgrades is starting at boot and running with:
sudo systemctl is-enabled unattended-upgrades # Check if starting at boot
sudo systemctl is-active unattended-upgrades # Check if running
Mine both were, so with that done, we can move on to services.
Configure ircd-hybrid
ircd-hybrid is easy to configure, but some features can be lacking and documentation sparse. However, the version in the Debian repos is usually very close to the latest stable release, and that is the key attribute we’re going for. For an IRC server that tries to implement the features of the new specification, try InspIRCd. However, the price for bleeding-edge is that the Debian repos do not yet have the latest stable version (at the time of this writing), so you’d have to manually download, install, and configure every upgrade.
While ircd-hybrid isn’t installed, it is available in the default repos, so we can install with:
sudo apt install ircd-hybrid
Confirm ircd-hybrid is starting at boot and running with:
sudo systemctl is-enabled ircd-hybrid
sudo systemctl is-active ircd-hybrid
Mine both were, so with that done, we can move on to configuration.
Now, edit the MOTD to let those connecting know any relevant information. Usually like a welcome message, what channels they should go to and for what, IRC usage/registration info, or any updates.
sudo vim /etc/ircd-hybrid/ircd.motd
For reference, mine looks like this:
Welcome to irc.josealerma.com!
------------------------------
This is the IRC server for the website
JoseALerma.com
By default, the only channel available is
#hello, and registration is not needed.
However, channels cannot be registered,
nor other features in a normal IRC server.
This IRC server is mainly for communicating
with the maintainer of JoseALerma.com. Please
do not attempt to use this as a hub for your
project. Instead, try open IRC servers like
freenode.net
If you're having trouble contacting the
maintainer of this site, try the more
traditional methods available at
contact.JoseALerma.com
The ircd-hybrid service can be restarted with:
sudo systemctl restart ircd-hybrid
Creating a password for the IRCop (often simply called “oper,” though distinct from channel opers) is not needed because users that can log in to the server are automatically considered operators. Try not to put it on a widely-used server, mmkay? Alternatively, specify users with multiple user = "username@127.0.0.1"; lines.
With that explanation out of the way, let’s dive into ircd-hybrid‘s config file:
sudo vim /etc/ircd-hybrid/ircd.conf
At the very least, add or change the following:
serverinfo {
name = "yourdomain.com";
description = "ircd-hybrid server";
network_name = "YourDomain";
network_desc = "IRC network for YourDomain";
};
listen {
port = 6667;
# host = "127.0.0.1";
};
We don’t specify a host IP because it may vary depending on the whims of AWS; however, one can be specified if using a static IP. For a much more detailed list of options, check out the ircd-hybrid reference.conf. At the very least, read through /etc/ircd-hybrid/ircd.conf in its entirety because there are sections I didn’t mention that should be configured, like admin{}; and shared{}; or channel{}; and general{}; (near the bottom).
That’s about it! Start a preferred client and see if we can connect with the public IPv4 address or DNS address (if configured).
Configure SSL/TLS
Configuring IRC to use SSH (via port 6697) is optional, but it’s pretty much a freebie because Debian already has openssh installed and the ircd-hybrid installer created a private key and certificate at /etc/ircd-hybrid/key/ using settings at /etc/ircd-hybrid/cert.cnf then added them to /etc/ircd-hybrid/ircd.conf during installation (this is why you go with the version in the repo).
All’s we need to do is create the PEM encoded Diffie-Hellman parameter file (as mentioned in ircd-hybrid reference.conf) with:
sudo openssl dhparam -out dhparam.pem
It warns “This is going to take a long time” and it wasn’t kidding, more like a whole 3 minutes. Jeez.
ircd-hybrid needs access to the file, so change ownership with:
sudo chown irc:irc dhparam.pem ircd.pem
We’ll also need to confirm, add, or change these lines in /etc/ircd-hybrid/ircd.conf:
serverinfo {
rsa_private_key_file = "/etc/ircd-hybrid/key/ircd.key";
ssl_certificate_file = "/etc/ircd-hybrid/key/ircd.pem";
ssl_dh_param_file = "/etc/ircd-hybrid/key/dhparam.pem";
ssl_cipher_list = "ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA:AES256-SHA";
};
The dhparam.pem file is used in the ciphers that include DHE in them. Just another layer of defense, really.
Confirm it’s working by, once again, starting a preferred client and checking if we can connect with the public IPv4 address or DNS address (if configured) using port 6697.
Tip
In HexChat, make sure the “Use SSL for all the servers on this network” and “Accept invalid SSL certificates” boxes are checked.
We can enforce SSL/TLS connections by only allowing port 6697 in the EC2 Security Group inbound connections, but configuring these connections can vary widely depending on the client used. Most anything can make a non-SSL/TLS connection, so it’s best not to restrict to port 6697. Although, it does impress the IRC geeks.
Now that SSL/TLS is configured for our IRC server, we can move on to the IRC Bouncer.
Configure ZNC
True to its name, an IRC Bouncer acts as a middle-man between the IRC server and the IRC client and provides nifty features. The main one we care about is that it always stays connected to the IRC server thereby keeping chat history. This allows clients to connect and disconnect without having to worry about missing conversations.
We’ll be using ZNC because it is already included in the Debian repos, but feel free to use others.
Once again, znc isn’t installed, but it is available in the default repos, so we can install with:
sudo apt install znc
Generate the initial configuration by running:
znc --makeconf
These are the settings I went with:
-- Global settings --
Listen on port (1025 to 65534): 6664
Listen using SSL (yes/no) [no]: yes
Listen using both IPv4 and IPv6 (yes/no) [yes]: no
Verifying the listener...
ok
Unable to locate pem file: [/home/default_user/.znc/znc.pem], creating it
Writing Pem file [/home/default_user/.znc/znc.pem]...
ok
Enabled global modules [webadmin]
-- Admin user settings --
Username (alphanumeric): UserNameHere
Enter password: very_good_password_here
Confirm password: same_very_good_password_here
Nick [UserNameHere]:
Alternate nick [UserNameHere_]:
Ident [UserNameHere]:
Real name [Got ZNC?]:
Bind host (optional):
Enabled user modules [chansaver, controlpanel]
Set up a network? (yes/no) [yes]:
-- Network settings --
Name [freenode]: YourDomain
Server host (host only): 127.0.0.1
Server uses SSL? (yes/no) [no]: yes # If SSL/TLS not configured, leave blank
Server port (1 to 65535) [6697]: # If SSL/TLS not configured, leave 6667
Server password (probably empty):
Initial channels: #defaultchannel
Enabled network modules [simple_away]
Writing config [/home/default_user/.znc/configs/znc.conf]...
ok
To connect to this ZNC you need to connect to it as your IRC server
using the port that you supplied. You have to supply your login info
as the IRC server password like this: user/network:pass.
Try something like this in your IRC client...
/server <znc_server_ip> +6664 UserNameHere:<pass>
To manage settings, users and networks, point your web browser to
https://<znc_server_ip>:6664/
Launch ZNC now? (yes/no) [yes]:
With the initial configuration complete, and znc running, we can do the complete configuration by using the webadmin module. As mentioned, point a web browser to the public IPv4 or public DNS address (if configured):
https://public_ip_or_dns:6664/

Then enter your ZNC username and password.

Alternatively, while ZNC isn’t running, by editing the config file:
vim ~/.znc/configs/znc.conf
Access to ZNC is not meant for normal users, which is why port 6664 is blocked to MyIP in the Security Group inbound connection settings. Instead, normal users can use port 6667 or IRCS port 6697 (if configured).
If connecting to ZNC with HexChat, the network settings will have to look like this:

Now that ZNC is installed and configured, we’re technically done! Unless our server restarts…
Daemonize ZNC
Currently, znc is running as the default user (you know the default user I’m talking about) and only because the configuration script started it. Problem is that the default user has root access, and that’s not good (if you have no problems with it, there are ways to configure a ZNC cronjob).
To fix this concern, we’ll leverage Debian’s use of systemd to configure ZNC as a system process (or “daemon,” as the Linux geeks say) using this Running ZNC as a system daemon guide.
First, make a znc user:
sudo useradd --create-home -d /var/lib/znc --system --shell /sbin/nologin --comment "Account to run ZNC daemon" --user-group znc
Next, stop all znc processes because we’ll be making a new ZNC config file:
sudo killall znc
Then, make the new configuration file for the znc user with the same settings as before:
sudo -u znc /usr/bin/znc --datadir=/var/lib/znc --makeconf
Except change the last option Launch ZNC now? (yes/no) [yes]: no because we’ll be messing with the config file next:
echo "PidFile = /var/run/znc/znc.pid" | sudo su - znc -s /bin/bash -c "tee -a /var/lib/znc/configs/znc.conf"
This ensures that ZNC makes a PID file; however, that location doesn’t exist, so we’ll have to create it and let the znc user have access to it:
sudo mkdir /var/run/znc/
sudo chown znc:znc /var/run/znc/
Now, we make the systemd service:
sudo vim /etc/systemd/system/znc.service
and make sure it looks like this:
[Unit]
Description=ZNC, an advanced IRC bouncer
After=network-online.target
[Service]
ExecStart=/usr/bin/znc -f --datadir=/var/lib/znc
User=znc
[Install]
WantedBy=multi-user.target
Furthermore, we start the service and enable it at boot:
sudo systemctl start znc
sudo systemctl enable znc
Finally, we confirm that it’s starting at boot and running:
sudo systemctl is-enabled znc
sudo systemctl is-active znc
Mine both were, and with that, the Debian 9 AMI IRC server is complete!
Results

The AWS EC2 instance is successfully running Debian 9 AMI as an IRC server with ZNC as an IRC Bouncer.
Conclusion
I learned a lot about a communication service that is over 30 years old. This is actually the most basic configuration, which is why it was much easier than when I made the CentOS 7 AMI Web and Mail Server with DDNS. The choice of Debian may have been a large part of why this article is half the length of the previous one.
While Debian doesn’t have the same security features as CentOS, it is as stable as a rock and therefore suitable for configuring key services.
It is my hope that this guide will continue to raise awareness and use of IRC. After all, it’s powerful and, surprisingly, free.
Jun. 26, 2019 Update: Fixed a typo in the InspIRCd link anchor. Spellchecker is not always your friend.
Jun. 27, 2019 Update: Fixed another typo. Spellchecker only works on words that aren’t words.
Feb. 06, 2021 Update: Fixed more typos. Yep, still checking. Muscle memory can change grammar.