UnrealIRCd and Anope¶
UnrealIRCd is the most widely deployed IRC server. The current series is 6.2.x; only the latest stable release receives security fixes, so plan to upgrade regularly (./unrealircd upgrade makes this painless). Anope provides NickServ, ChanServ, and friends. Its stable series is 2.0.x; 2.1.x is the development series (renamed modules and modernized config, see notes below).
See also: IRC guide · Command reference · irssi
Historical note: earlier versions of this page covered UnrealIRCd 3.2 and Anope 1.8 on CentOS 5 and FreeBSD 8. Both are long end-of-life and their config formats are incompatible with current releases. The lessons that still apply are kept in Legacy Lessons.
Ports and Firewall¶
| Port | Purpose | Expose publicly? |
|---|---|---|
| 6697 | Client connections over TLS | Yes |
| 6667 | Plaintext clients | Preferably no (or localhost/LAN only) |
| 6900 | Server-to-server links (TLS) | Only if you link a second IRCd |
| 7000 (127.0.0.1) | Anope services link | Never — bind to localhost |
sudo firewall-cmd --permanent --zone=public --add-port=6697/tcp
sudo firewall-cmd --reload
On the homelab, forward 6697 on OPNsense to the IRC host and leave everything else closed.
Install UnrealIRCd 6 from Source¶
1. Dependencies¶
# Fedora / RHEL / Rocky (EPEL for some libs)
sudo dnf install gcc gcc-c++ make pkgconf-pkg-config openssl-devel \
pcre2-devel libargon2-devel libsodium-devel c-ares-devel libcurl-devel jansson-devel
# Debian / Ubuntu
sudo apt install build-essential pkg-config gdb libssl-dev libpcre2-dev \
libargon2-dev libsodium-dev libc-ares-dev libcurl4-openssl-dev
Missing libraries are fine — UnrealIRCd bundles fallbacks — but system libraries get security updates from your distro.
2. Dedicated user (never run an IRCd as root)¶
sudo useradd -m -s /bin/bash unrealircd
sudo -iu unrealircd
3. Download, verify, build¶
curl -fsSLO https://www.unrealircd.org/downloads/unrealircd-latest.tar.gz
tar xzf unrealircd-latest.tar.gz
cd unrealircd-6.*/
./Config
make
make install
./Config is interactive; the defaults are sensible (install to ~/unrealircd, TLS on, generate a self-signed cert). It saves your answers to config.settings.
Automating ./Config (an open question in my old notes): after the first run, ./Config -quick reuses the saved answers non-interactively, which is what ./unrealircd upgrade does too. That makes it usable from Ansible: run it once by hand, keep config.settings, then ./Config -quick && make && make install in the playbook.
4. Create the config¶
cd ~/unrealircd
cp conf/examples/example.conf conf/unrealircd.conf
Essential Configuration¶
Edit ~/unrealircd/conf/unrealircd.conf. The example file is heavily commented; these are the blocks you must change.
Server identity¶
me {
name "irc.andrewmercer.net";
info "Andrew Mercer IRC";
sid "001"; /* unique 3-char server ID: digit + 2 alnum */
}
admin {
"Andrew Mercer";
"andrew";
"[email protected]";
}
Network settings and cloak keys¶
Generate cloak keys rather than inventing them:
cd ~/unrealircd && ./unrealircd gencloak
set {
network-name "umoswg";
default-server "irc.andrewmercer.net";
services-server "services.irc.andrewmercer.net";
sasl-server "services.irc.andrewmercer.net";
help-channel "#andrewmercer";
cloak-prefix "umoswg";
kline-address "[email protected]";
modes-on-connect "+ixw";
modes-on-join "+nt";
cloak-keys {
"<key 1 from gencloak>";
"<key 2 from gencloak>";
"<key 3 from gencloak>";
}
}
Keep cloak keys secret, and identical on every server in the network. Changing them changes every user's cloaked host (and invalidates bans written against cloaks).
Listen blocks¶
listen { ip *; port 6697; options { tls; } }
listen { ip 127.0.0.1; port 6667; } /* local plaintext only */
listen { ip 127.0.0.1; port 7000; options { serversonly; } } /* Anope */
/* listen { ip *; port 6900; options { tls; serversonly; } } only if linking IRCds */
Operators¶
UnrealIRCd 6 does not want plaintext oper passwords. Hash one:
cd ~/unrealircd && ./unrealircd mkpasswd
# Enter password → prints an $argon2id$... hash
oper andrew {
class opers;
mask { 192.168.2.0/24; 10.10.10.0/24; } /* LAN + WireGuard only */
password "$argon2id$v=19$m=6144,t=2,p=2$...";
operclass netadmin-with-override;
swhois "is a Network Administrator";
vhost netadmin.andrewmercer.net;
}
operclassdecides permissions.netadmin-with-overrideallows/samodeand overriding channel restrictions (the old 3.2can_overrideflag). Plainnetadmindoes not.- Restricting
maskto trusted networks means a leaked password alone isn't enough. - Log in from a client with
/oper andrew <password>.
Restrict which channels can be created¶
To make users able to join only specific channels (replaces the old 3.2 chrestrict):
deny channel {
channel "*";
reason "Only official channels are available on this server";
}
allow channel {
channel "#andrewmercer";
}
allow channel {
channel "#lab";
}
IRC operators with override can still join anything.
Check and apply¶
./unrealircd configtest # validate without starting
./unrealircd start
./unrealircd rehash # reload config after edits (or /rehash as oper)
./unrealircd status
tail -f logs/ircd.log
TLS Certificates¶
make install generates a self-signed certificate at ~/unrealircd/conf/tls/server.cert.pem and server.key.pem. Clients will reject it unless they disable verification or pin it. Use a real certificate instead.
Let's Encrypt¶
Issue a certificate for irc.andrewmercer.net with certbot (or the certbot-manager tooling), then install a deploy hook that copies it where UnrealIRCd expects it and reloads TLS without a restart:
# /etc/letsencrypt/renewal-hooks/deploy/unrealircd.sh
#!/usr/bin/env bash
set -euo pipefail
DOMAIN="irc.andrewmercer.net"
DEST="/home/unrealircd/unrealircd/conf/tls"
install -o unrealircd -g unrealircd -m 0644 "/etc/letsencrypt/live/$DOMAIN/fullchain.pem" "$DEST/server.cert.pem"
install -o unrealircd -g unrealircd -m 0600 "/etc/letsencrypt/live/$DOMAIN/privkey.pem" "$DEST/server.key.pem"
sudo -u unrealircd /home/unrealircd/unrealircd/unrealircd reloadtls
sudo chmod 755 /etc/letsencrypt/renewal-hooks/deploy/unrealircd.sh
Verify from outside:
openssl s_client -connect irc.andrewmercer.net:6697 -servername irc.andrewmercer.net </dev/null \
| openssl x509 -noout -subject -issuer -dates
If you'd rather name the files differently, point the
set { tls { certificate "..."; key "..."; } }block at them instead of renaming. The old 3.2 behaviour of only looking forserver.cert.pem/server.key.pemis gone.
Run with systemd¶
Replaces the old SysV /etc/init.d/unreal + chkconfig script.
# /etc/systemd/system/unrealircd.service
[Unit]
Description=UnrealIRCd
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=unrealircd
Group=unrealircd
WorkingDirectory=/home/unrealircd/unrealircd
ExecStart=/home/unrealircd/unrealircd/bin/unrealircd -F
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now unrealircd
sudo systemctl reload unrealircd # rehash
journalctl -u unrealircd -f
-F keeps UnrealIRCd in the foreground so systemd supervises it directly.
Upgrading¶
sudo -iu unrealircd
cd ~/unrealircd
./unrealircd upgrade # downloads, verifies signature, rebuilds with saved settings
./unrealircd restart # or: sudo systemctl restart unrealircd
Read the release notes first; 6.x minor releases occasionally change config defaults.
Anope Services¶
Install (2.0 stable)¶
sudo useradd -m -s /bin/bash anope
sudo -iu anope
curl -fsSLO https://github.com/anope/anope/archive/refs/tags/2.0.20.tar.gz
tar xzf 2.0.20.tar.gz && cd anope-2.0.20
./Config # install dir defaults to ~/anope; CMake-based
cd build
make && make install
Check https://www.anope.org/ for the current stable version number. Anope also publishes an official Docker image (anope/anope).
Configure Anope¶
cd ~/anope/conf
cp example.conf services.conf
Key blocks in services.conf:
uplink
{
host = "127.0.0.1"
ipv6 = no
ssl = no /* fine on localhost; TLS is supported if linking remotely */
port = 7000
password = "<long random link password>"
}
serverinfo
{
name = "services.irc.andrewmercer.net"
description = "Services for umoswg"
pid = "data/services.pid"
motd = "conf/services.motd"
}
module
{
name = "unreal4" /* 2.0.x module; supports UnrealIRCd 4, 5 and 6 */
use_server_side_mlock = yes
use_server_side_topiclock = yes
}
networkinfo
{
networkname = "umoswg"
nicklen = 31
userlen = 10
hostlen = 64
chanlen = 32
}
Make yourself services root (replaces 1.8's ServicesRoot directive):
oper
{
name = "andrew"
type = "Services Root"
require_oper = yes
}
Anope 2.1 differences: the protocol module is renamed
unrealircd(fromunreal4),uplink:ipv6becomesuplink:protocol, and insecure password hashing modules can no longer be the primary encryption method. Read the upgrading guide before switching.
Matching UnrealIRCd blocks¶
Add to unrealircd.conf:
link services.irc.andrewmercer.net {
incoming {
mask 127.0.0.1;
}
password "<same long random link password>";
class servers;
}
ulines {
services.irc.andrewmercer.net;
}
The services-server and sasl-server entries in the set { } block shown earlier are what make SASL login work for clients.
Then:
sudo -u unrealircd ~unrealircd/unrealircd/unrealircd rehash
sudo -iu anope
~/anope/bin/services --nofork --debug # first run: watch it link
# Ctrl+C once it works, then:
~/anope/bin/anoperc start
tail -f ~/anope/data/logs/services.log.*
A successful link shows Anope introducing NickServ, ChanServ, etc., and /map in your client lists services.irc.andrewmercer.net.
Anope 2.1 ships an example systemd unit; for 2.0, a unit modelled on the UnrealIRCd one above with ExecStart=/home/anope/anope/bin/services --nofork works.
Troubleshooting¶
| Error | Cause / fix |
|---|---|
Link denied (No matching link configuration) |
The serverinfo:name in services.conf doesn't exactly match the link <name> in unrealircd.conf, or the incoming IP doesn't match mask. |
Link denied (Authentication failed) |
Link passwords differ. |
| Services link, but SASL fails | Missing sasl-server / services-server in set { }, or services name not in ulines. |
| Clients get certificate errors | Still using the self-signed cert — install Let's Encrypt and ./unrealircd reloadtls. |
./unrealircd upgrade warns about a key mismatch |
Very old 6.1.x builds predate the current signing key; upgrade manually once from the tarball. |
| OpenSSL version mismatch warning | See below — rebuild after a major OpenSSL upgrade. |
Legacy Lessons¶
Kept from the 3.2/1.8 era because the underlying principles still hold:
- Rebuild after OpenSSL major upgrades. UnrealIRCd 3.2 warned
OpenSSL version mismatch: compiled for 0.9.8l ... library is 0.9.8eand could crash. Any IRCd compiled against OpenSSL must be rebuilt when the library's major version changes (on modern installs,./unrealircd upgradeor./Config -quick && make && make install). Having two OpenSSL installs (/usrand/usr/local) was the root cause back then. - A leftover
$INSTALLenvironment variable brokemake install(Error 126in the bundled TRE library). Checkenvwhen a build fails in a strange place. - Anope's name must match the IRCd link block exactly. This was the cause of every "Link denied" I hit on CentOS and FreeBSD, and it's still the #1 linking problem.
- The FreeBSD port expected a dedicated user/group (
ircdruin the old port). Ports and packages often assume a run-as user — read the port'sMakefile/pkg-message. - "Anope will not work over SSL" was true for 1.8; Anope 2.x can link over TLS, though on localhost plaintext is fine.