Build A Modular IRC Server In 2026 (Inspircd + Anope)

You saw the simplicity of Ergo Chat.
Now you think about how to build a modern, modular IRC server – like in the old days.

  • Inspircd is a modern IRC server.
  • Anope is a services bot.

Together they form the base of a stable and reliable communications service.

In 2026 IRC may not be the primary comms channel, but it’s perfect for secondary/backup.

The Goal

The architecture:

IRC Client
    |
  TLS
    |
Inspircd
    |
local link
    |
Anope (NickServ, ChanServ)

  1. Inspircd is the IRC server – the clients connect directly using TLS (port 6697).
  2. Anope connects to Inspircd locally – it provides services.
  3. TLS is served by certbot through Let’s Encrypt.
  4. SASL authentication is enabled for modern clients.

Prerequisites

  1. A Debian Linux or compatible server.
  2. root user, or sudo access.
  3. Open firewall ports (80 and 6697).
  4. Domain pointed to the server (e.g. irc.domain.tld).
  5. Certificates for the domain (Let’s Encrypt).

Install The Software

Inspircd and Anope are in the Debian repository.
apt can install them:

sudo apt update
sudo apt install -y inspircd anope certbot

After the installation the configurations are in:

  • etc/inspircd
  • etc/anope

The example configurations:

  • usr/share/doc/inspircd/examples
  • usr/share/doc/anope/examples

Issue The Certificates

certbot is used to issue Let’s Encrypt certificates.
With the --standalone option certbot starts a temporary http server on port 80.

I use the irc.silentarchitect.org as an example. Change it to your own domain.

sudo certbot certonly --standalone \
-d irc.silentarchitect.org \
--non-interactive \
--agree-tos \
-m tom@tomsitcafe.com

The certificates are installed in the /etc/letsencrypt/ directory by default.
By default the irc user of Inspircd cannot read them.

Copy the certs to the Inspircd config directory.

Create the certs dir:

sudo mkdir -p /etc/inspircd/certs
sudo chown irc:irc /etc/inspircd/certs

Copy the certs:

sudo cp /etc/letsencrypt/live/irc.silentarchitect.org/fullchain.pem /etc/inspircd/certs/
sudo cp /etc/letsencrypt/live/irc.silentarchitect.org/privkey.pem /etc/inspircd/certs/
sudo chown -R irc:irc /etc/inspircd/certs
sudo chmod 600 /etc/inspircd/certs/privkey.pem

Don’t forget to create a deploy hook that copies the certs automatically when certbot renews them.

Configure The Inspircd

The Inspircd configuration is in the /etc/inspircd/ directory.
An example inspircd.conf file is pre-deployed.
More examples can be found in the /usr/share/doc/inspircd/examples/ directory.

Backup the original config.

sudo mv /etc/inspircd/inspircd.conf /etc/inspircd/inspircd.conf.orig

In the Inspircd configuration the order of some blocks matters.
Some modules and definitions must appear before they are referenced.

Set up the network name and domain.
This will be visible for the connecting clients.
The name field must match with the DNS record.

<server
name="irc.silentarchitect.org"
description="SilentArchitectNetwork IRC server"
network="SilentArchitectNetwork">

Load the most important Inspircd modules.

<module name="ssl_gnutls">
<module name="spanningtree">
<module name="account">
<module name="services">
<module name="cloak">
<module name="cloak_sha256">
ModulePurpose
ssl_gnutlsTLS encryption
spanningtreeRequired for services and server linking
accountIRC account handling
servicesEnables the services support
cloakHides client IP/hostname
cloak_sha256Uses SHA256 for cloaking

The TLS configuration points to the certs you already set up.
Create an sslprofile and use it in the port binding.

<sslprofile name="Clients"
provider="gnutls"
cafile=""
certfile="/etc/inspircd/certs/fullchain.pem"
crlfile=""
hash="sha3-256"
keyfile="/etc/inspircd/certs/privkey.pem"
mindhbits="2048"
outrecsize="2048"
priority="NORMAL"
requestclientcert="yes"
strictpriority="no">
<bind
address=""
port="6697"
type="clients"
sslprofile="Clients"
defer="0"
free="no">

Port 6697 is the standard TLS IRC port.

Connection classes allow you to define limits, flood protection, and different client tiers.

<connect name="main" allow="*" modes="+x">

For a full working configuration example see below.

Configure Anope Services

Backup the original config.

sudo mv /etc/anope/services.conf /etc/anope/services.conf.orig

Example config directory: /usr/share/doc/anope/examples

Edit the configuration file: /etc/anope/services.conf

The minimal configuration sets up the IRC server link and the services info.

uplink
{
host = "127.0.0.1"
ipv6 = no
ssl = no
port = 7000
password = "passwordforservices"
}
serverinfo
{
name = "services.silentarchitect.org"
description = "Services of The Silent Architect"
pid = "/run/anope/anope.pid"
motd = "services.motd"
}
module
{
name = "inspircd3"
use_server_side_mlock = yes
use_server_side_topiclock = yes
}
networkinfo
{
networkname = "SilentArchitectNetwork"
nicklen = 31
userlen = 10
hostlen = 64
chanlen = 32
modelistsize = 100
vhost_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-"
allow_undotted_vhosts = false
disallow_start_or_end = ".-"
}

Traditionally IRC services use flat files as a database backend.
A flat file database can easily handle ~10k users / 50k accounts.

Load the enc_sha256 module to hash passwords.

module { name = "enc_sha256" }
module {
name = "db_flatfile"
database = "anope.db"
keepbackups = 3
}

For a full working configuration see below.

Test The Server

After starting the services you should be able to connect with a client:

irc.silentarchitect.org:6697 (TLS)

Example with weechat:

/server add silent irc.silentarchitect.org/6697 -ssl
/connect silent

Final Thoughts

Inspircd + Anope gives you a modular IRC architecture.

The IRC daemon handles connections and routing.
Services handle identity, channels, and network state.

This separation makes the system flexible, stable, and easy to extend.

For small private networks this stack can run comfortably on even a small VPS.

Full Inspircd Configuration

The following configurations are simplified examples of a working single-server IRC network.

<server
        name="irc.silentarchitect.org"
        description="SilentArchitectNetwork IRC server"
        network="SilentArchitectNetwork">

<admin
       name="Tom"
       description="tom"
       email="tom@tomsitcafe.com">

<module name="ssl_gnutls">
<module name="hidechans">
<module name="spanningtree">
<module name="account">
<module name="services">
<module name="conn_umodes">
<module name="sha2">
<module name="cloak">
<module name="cloak_sha256">

<cloak method="hmac-sha256"
       key="lohshal0eemei3eiFeGhiv1aep8shi"
       prefix="silent"
       suffix="user">

<sslprofile name="Clients"
            provider="gnutls"
            cafile=""
            certfile="/etc/inspircd/certs/fullchain.pem"
            crlfile=""
            hash="sha3-256"
            keyfile="/etc/inspircd/certs/privkey.pem"
            mindhbits="2048"
            outrecsize="2048"
            priority="NORMAL"
            requestclientcert="yes"
            strictpriority="no">

<bind
      address=""
      port="6697"
      type="clients"
      sslprofile="Clients"
      defer="0"
      free="no">

<files motd="inspircd.motd">

<connect
    name="main"
    allow="*"
    maxchans="20"
    timeout="20"
    pingfreq="2m"
    hardsendq="1M"
    softsendq="64K"
    recvq="64K"
    threshold="10"
    commandrate="20"
    fakelag="yes"
    localmax="10"
    globalmax="10"
    resolvehostnames="no"
    useident="no"
    limit="5000"
    modes="+x">

<connect
    name="opers"
    allow="*"
    maxchans="60"
    timeout="20"
    pingfreq="2m"
    hardsendq="1M"
    softsendq="256K"
    recvq="128K"
    threshold="50"
    commandrate="100"
    fakelag="no"
    localmax="50"
    globalmax="50"
    resolvehostnames="no"
    useident="no"
    limit="5000"
    modes="+x">

<dns timeout="5">

<maxlist chan="*" limit="100">

<options
         prefixquit="Quit: "
         suffixquit=""
         prefixpart="&quot;"
         suffixpart="&quot;"
         syntaxhints="no"
         cyclehostsfromuser="no"
         announcets="yes"
         allowmismatch="no"
         defaultbind="auto"
         maskinlist="yes"
         maskintopic="yes"
         pingwarning="15"
         serverpingfreq="1m"
         splitwhois="no"
         defaultmodes="not"
         xlinemessage="You're banned! Email tom@tomsitcafe.com with the ERROR line below for help."
         xlinequit="%fulltype%: %reason%"
         modesinlist="opers"
         extbanformat="name"
         exemptchanops="filter:o nickflood:o nonick:v regmoderated:o"
         invitebypassmodes="yes"
         nosnoticestack="no">

<performance
             netbuffersize="10240"
             somaxconn="128"
             softlimit="12800"
             clonesonconnect="yes"
             timeskipwarn="2s"
             quietbursts="yes">

<security
          announceinvites="dynamic"
          hideservices="no"
          flatlinks="no"
          hidekills=""
          hideservicekills="yes"
          hidesplits="no"
          maxtargets="20"
          customversion=""
          restrictbannedusers="yes"
          genericoper="no"
          userstats="Pu">

<limits
        maxaway="200"
        maxchan="60"
        maxhost="64"
        maxuser="10"
        maxkey="30"
        maxkick="300"
        maxmodes="20"
        maxnick="30"
        maxquit="300"
        maxreal="130"
        maxtopic="330">

<log method="file"
     level="normal"
     type="* -USERINPUT -USEROUTPUT"
     target="/var/log/inspircd.log">

<whowas
        groupsize="10"
        maxgroups="10000"
        maxkeep="7d"
        nickupdate="yes">

<insane
        hostmasks="no"
        ipmasks="no"
        nickmasks="no"
        trigger="95.5">

<link name="services.silentarchitect.org"
      ipaddr="127.0.0.1"
      port="7000"
      allowmask="127.0.0.0/8"
      sendpass="passwordforservices"
      recvpass="passwordforservices">

<uline server="services.silentarchitect.org" silent="yes">
<bind address="127.0.0.1" port="7000" type="servers">
<module name="cap">
<module name="sasl">
<sasl target="services.silentarchitect.org" requiressl="no">

<module name="alias">
<alias text="BOTSERV"  replace="PRIVMSG $requirement :$2-" requires="BotServ"  service="yes">
<alias text="CHANSERV" replace="PRIVMSG $requirement :$2-" requires="ChanServ" service="yes">
<alias text="GLOBAL"   replace="PRIVMSG $requirement :$2-" requires="Global"   service="yes" operonly="yes">
<alias text="HOSTSERV" replace="PRIVMSG $requirement :$2-" requires="HostServ" service="yes">
<alias text="MEMOSERV" replace="PRIVMSG $requirement :$2-" requires="MemoServ" service="yes">
<alias text="NICKSERV" replace="PRIVMSG $requirement :$2-" requires="NickServ" service="yes">
<alias text="OPERSERV" replace="PRIVMSG $requirement :$2-" requires="OperServ" service="yes" operonly="yes">
<alias text="STATSERV" replace="PRIVMSG $requirement :$2-" requires="StatServ" service="yes">
<alias text="BS" replace="PRIVMSG $requirement :$2-" requires="BotServ"  service="yes">
<alias text="CS" replace="PRIVMSG $requirement :$2-" requires="ChanServ" service="yes">
<alias text="GL" replace="PRIVMSG $requirement :$2-" requires="Global"   service="yes" operonly="yes">
<alias text="HS" replace="PRIVMSG $requirement :$2-" requires="HostServ" service="yes">
<alias text="MS" replace="PRIVMSG $requirement :$2-" requires="MemoServ" service="yes">
<alias text="NS" replace="PRIVMSG $requirement :$2-" requires="NickServ" service="yes">
<alias text="OS" replace="PRIVMSG $requirement :$2-" requires="OperServ" service="yes" operonly="yes">
<alias text="SS" replace="PRIVMSG $requirement :$2-" requires="StatServ" service="yes">
<alias text="ID"       format="*" replace="PRIVMSG $requirement :IDENTIFY $2-" requires="NickServ" service="yes">
<alias text="IDENTIFY" format="*" replace="PRIVMSG $requirement :IDENTIFY $2-" requires="NickServ" service="yes">
<alias text="LOGIN"    format="*" replace="PRIVMSG $requirement :IDENTIFY $2-" requires="NickServ" service="yes">
<alias text="LOGOUT" format="*" replace="PRIVMSG $requirement :LOGOUT" requires="NickServ" service="yes">
<badnick nick="BotServ"  reason="Reserved for a network service">
<badnick nick="ChanServ" reason="Reserved for a network service">
<badnick nick="Global"   reason="Reserved for a network service">
<badnick nick="HostServ" reason="Reserved for a network service">
<badnick nick="MemoServ" reason="Reserved for a network service">
<badnick nick="NickServ" reason="Reserved for a network service">
<badnick nick="OperServ" reason="Reserved for a network service">
<badnick nick="StatServ" reason="Reserved for a network service">
<exemptfromfilter target="BotServ">
<exemptfromfilter target="ChanServ">
<exemptfromfilter target="Global">
<exemptfromfilter target="HostServ">
<exemptfromfilter target="MemoServ">
<exemptfromfilter target="NickServ">
<exemptfromfilter target="OperServ">
<exemptfromfilter target="StatServ">

Full Anope Configuration

The following configurations are simplified examples of a working single-server IRC network.
Copy the different services’ configuration files into the config directory.
Configure the services hostname in them.
Services files to configure:

  • nickserv.conf
  • chanserv.conf
  • operserv.conf
  • hostserv.conf
  • memoserv.conf
  • global.conf

The main services.conf file should look like this:

uplink
{
        host = "127.0.0.1"
        ipv6 = no
        ssl = no
        port = 7000
        password = "passwordforservices"
}

serverinfo
{
        name = "services.silentarchitect.org"
        description = "Services of The Silent Architect"
        pid = "/run/anope/anope.pid"
        motd = "services.motd"
}

module
{
        name = "inspircd3"
        use_server_side_mlock = yes
        use_server_side_topiclock = yes
}

networkinfo
{
        networkname = "SilentArchitectNetwork"
        nicklen = 31
        userlen = 10
        hostlen = 64
        chanlen = 32
        modelistsize = 100
        vhost_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-"
        allow_undotted_vhosts = false
        disallow_start_or_end = ".-"
}

options
{
        casemap = "ascii"
        strictpasswords = yes
        badpasslimit = 5
        badpasstimeout = 1h
        updatetimeout = 5m
        expiretimeout = 30m
        readtimeout = 5s
        timeoutcheck = 3s
        retrywait = 60s
        hideprivilegedcommands = yes
        hideregisteredcommands = yes
        languages = "ca_ES.UTF-8 de_DE.UTF-8 el_GR.UTF-8 es_ES.UTF-8 fr_FR.UTF-8 hu_HU.UTF-8 it_IT.UTF-8 nl_NL.UTF-8 pl_PL.UTF-8 pt_PT.UTF-8 ru_RU.UTF-8 tr_TR.UTF-8"
}

module { name = "enc_sha256" }

module {
        name = "db_flatfile"
        database = "anope.db"
        keepbackups = 3
}

include {
    type = "file"
    name = "nickserv.conf"
}

include {
    type = "file"
    name = "chanserv.conf"
}

include {
    type = "file"
    name = "operserv.conf"
}

include {
    type = "file"
    name = "hostserv.conf"
}

include {
    type = "file"
    name = "memoserv.conf"
}

include {
    type = "file"
    name = "global.conf"
}

log
{
        target = "services.log"
        bot = "Global"
        logage = 7
        users = "connect disconnect nick"
        rawio = no
        debug = no
}

module { name = "help" }
module { name = "m_sasl" }

opertype {
    name = "ServicesRoot"
    commands = "*"
    privileges = "*"
}

oper {  
    name = "tom"
    type = "ServicesRoot"
}

Next Steps

Once the basic server is running you may want to add:

  • IRC operators
  • channel registration policies
  • spam protection modules
  • a Matrix or Discord bridge

Inspircd’s modular design makes these extensions easy to integrate.

Final Thoughts

The Silent Architect IRC network is a quiet place.

Discussion is slow.
Silence is normal.
People speak when they have something worth saying.

Idle minds are welcome.
Noise is not.

Server: irc.silentarchitect.org
Port: 6697 (TLS)
Channel: #ghostops

Discord invite link: https://discord.gg/nxvna45STM

Leave a comment