Open-source project
znc/znc avatar
znc/znc

ZNC keeps the IRC connection when your client goes away

Official repository for the ZNC IRC bouncer

2,127 stars406 forksC++Apache-2.0

At a glance

What is it?
ZNC is a C++ IRC bouncer that holds network connections open between client sessions and replays channel backlog on reconnect. Its real decisions sit in the two buffer answers, the optional module libraries, and a Docker image that compiles in what the docs call optional.
Who is it for?
ZNC earns its keep on a link that drops, for an operator who wants one persistent connection per configured network and accepts compiling it from source. It is wasted on a single always-connected client, and it offers nothing to anyone hunting packaged plugins, since every module path starts from that build.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 21 days ago.
What is it written in?
Mainly C++, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 15, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The connection outlives the client that made it

A bouncer sits between an IRC client and the networks it joins, and the job ZNC does is deliberately narrow: keep the server connections up when no client is attached. The tree shows the shape of that. Under the data dir, which defaults to `~/.znc`, the users directory holds per-user data, and inside it sits a moddata directory plus a directory for each network configured. State belongs to the account, not to whichever program happened to be open.

Attaching a client is then an authentication step rather than a network setup step. The README gives the client side as `username:password` supplied as the server password, for instance `/pass user:pass`, and points at `/msg *status help` once you are connected.

Backlog is the part you actually notice. On connect you get a buffer replay for each channel showing what was said last, so lines keep arriving even during the hours when nothing was listening for them.

Two answers from --makeconf set your memory bill

Running `znc --makeconf` asks two questions that the documentation admits are not easy to understand, and both decide how much state ZNC keeps. The first is "Number of lines to buffer per channel": that is how many lines the replay for each channel consists of. The second is "Would you like to keep buffers after replay": disabled, you get the playback once and it is deleted; enabled, it stays, which matters if you regularly run more than one client.

The memory warning is attached to the first answer rather than the second. Raising the line count can greatly increase memory usage when you host many users, and the guidance is that the default value should be fine for most setups.

So the two defaults are not cosmetic. They choose between a transient replay and a persistent archive, and that choice is made once, interactively, before any traffic exists.

Building from source with the CMake toolchain

The core toolchain is listed as GNU make, pkg-config, GCC 8 or clang 5 and CMake 3.13, and the documented install is a separate build directory:

shell
mkdir build
cd build
cmake ..
make
make install

If you would rather click through the options, `cmake-gui` or `ccmake` drive the same CMake build interactively. A `configure.sh` wrapper remains for people migrating from the old script, accepting the same parameters as `./configure` while passing CMake-style ones through.

Three escape hatches are worth reading first. FreeBSD picks the base OpenSSL by default, and `-DOPENSSL_ROOT_DIR=/usr/local` forces the ports version instead. A custom path goes through `-DCMAKE_INSTALL_PREFIX`, so `cmake .. -DCMAKE_INSTALL_PREFIX=/home/user/znc` keeps everything inside a per-user prefix. When the build stalls on a dependency, `cmake --system-information` prints what CMake detected.

Optional libraries decide which modules exist at all

Everything past the core list switches a module on or off, and each entry names its own dependency. TLS wants openssl 1.1.1 or later, with openssl-dev, openssl-devel or libssl-dev given as the packages to try, and macOS users are pointed at a Homebrew OpenSSL instead of the system copy. Character encodings come from ICU, installed as `libicu4-dev`, while interface translation wants Boost.Locale 1.70 or later with gettext as a build dependency. `cyrusauth` needs cyrus-sasl2, and Argon2 password hashing needs libargon2.

The scripting bindings hold the detail that trips people up. For both modperl and modpython, SWIG is a build dependency only when building from git. modpython also lists perl as a build dependency, so a Python-only setup still drags perl onto the build machine, and python 3.4+ with its bundled libpython is the runtime half of that pair.

The Alpine image compiles in what the README calls optional

The repository Dockerfile makes a different set of choices from the optional requirements list, and it reads like a configuration file. The base image is alpine:3.23, and the CMake flags arrive as one argument string that fixes the install prefix and turns four optional features on at once:

dockerfile
FROM alpine:3.23
ARG CMAKEFLAGS="-DVERSION_EXTRA=${VERSION_EXTRA} -DCMAKE_INSTALL_PREFIX=/opt/znc -DWANT_CYRUS=YES -DWANT_PERL=YES -DWANT_PYTHON=YES -DWANT_ARGON=YES"
VOLUME /znc-data
ENTRYPOINT ["/entrypoint.sh"]

The build runs inside the image with build-base, cmake, swig and the matching development headers, then `apk del build-dependencies` strips those headers before the image is finished, while a system user is created with `adduser -S znc`. Runtime helpers `su-exec` and `tini` arrive alongside, and startup scripts are copied into `/startup-sequence/` from two directories. Persistent state belongs in the `/znc-data` volume.

One detail ties that image to the repository rather than to a download: it copies the checkout with `COPY . /znc-src` and builds that tree, and since the repository carries no GitHub releases, there is no published version for `VERSION_EXTRA` to be filled from unless a build supplies one.

The config warning sits beside the signal that rewrites it

Editing the config by hand while ZNC runs is discouraged, and the supported route is named instead: the `webadmin` and `controlpanel` modules. Changes made at runtime can still be pushed back to disk, either by sending the process a signal with `pkill -SIGUSR1 znc` or by asking from inside with `/msg *status saveconfig`. That pair is why the warning reads as advice rather than a hard rule.

The layout around it is slightly inconsistent in the documentation's own telling. One section says `znc --makeconf` produces a configuration file in `~/.znc`, while the file locations section puts `znc.conf` and backups of older configs inside a configs subdirectory. The same data dir also holds modules, moddata for global module settings, and the users tree. Only one file sits loose at the top: `znc.pem`, the server certificate ZNC uses for listening.

Two commands create that state, and the second one is what you need if you plan to serve SSL:

shell
znc --makeconf
znc --makepem

Three module languages, three different loading paths

A module can be written in C++, python or perl, and each language reaches the runtime by a different road. C++ modules are compiled: save the source in the modules source directory and run make, or hand the same job to the `znc-buildmod` shell script. Perl modules load through the global `ModPerl` module and Python modules through `ModPython`, so neither of those languages needs a compiler at the point where you install the file.

Anything added later, including modules written by hand, can be dropped into `<data dir>/modules`, which resolves to `~/.znc/modules` in a default install. Global module settings are not kept beside the module code. They live in the moddata directory, which is why the skin name chosen in `webadmin` is remembered there instead of in a module file.

Finding out what a loaded module offers is a two step job: `/msg *status listmods` returns the loaded set, and each entry answers on its own channel with `/msg *modulename help`.

Where a bouncer is the wrong answer

ZNC loses its reason to exist in a few honest cases. If one machine runs one client that never drops, there is nothing to hold open, and the bouncer adds a process, a data dir and a password between you and the network. If you want packaged or downloadable plugins, nothing here points that way: the build comes from source, and every module path depends on that tree being compiled with SWIG and the matching development headers present.

The TLS story is documented for one direction only. `znc.pem` is described as the server certificate ZNC uses for listening, which is the client to bouncer hop, and the README does not explain how that certificate is validated on the outgoing connection to each network. Deployments where upstream TLS matters have to settle that separately.

For current code, the README gives one command, and the `.gitmodules` file at the top of the tree is the reason the recursive flag exists:

shell
git clone https://github.com/znc/znc.git --recursive

Questions go to https://znc.in/ or to the #znc channel on Libera.Chat, listed as `ircs://irc.libera.chat:6697/#znc`, so port 6697 over TLS is the address to point a client at. The last push on the default branch was on 2026-09-24.

Editorial conclusion

ZNC earns its keep on a link that drops, for an operator who wants one persistent connection per configured network and accepts compiling it from source. It is wasted on a single always-connected client, and it offers nothing to anyone hunting packaged plugins, since every module path starts from that build. Verify first the part the documentation leaves open, the certificate check on the outgoing connection to each network, then settle the two buffer answers, because the line count sets memory once more than a few users share the daemon.

Frequently asked questions

What is ZNC and who needs it?

ZNC is an IRC bouncer written in C++, with its homepage at https://znc.in/. It keeps your network connections open while no client is attached and replays buffered channel lines on connect, so it suits users whose links drop. Its per-user data directory holds a directory for each network configured.

How do I build ZNC and create a first config?

The core requirements are GNU make, pkg-config, GCC 8 or clang 5 and CMake 3.13, and the install runs cmake, make and make install from a build directory. Run `znc --makeconf` to write the config, and add `znc --makepem` when you intend to serve SSL.

How much memory do ZNC channel buffers use?

Two answers given during `znc --makeconf` decide it: how many lines to buffer per channel, and whether buffers are kept after replay. Raising the line count can greatly increase memory usage when you host many users, and the default value is described as fine for most setups.

How do I load a ZNC module I wrote myself?

Write it in C++, python or perl. C++ modules are compiled by saving them in the modules source dir and running make, or with `znc-buildmod`; perl and python modules load through the global `ModPerl` and `ModPython` modules. Modules added later can be dropped into `<data dir>/modules`.

Where does ZNC store its files?

The data dir defaults to `~/.znc` and holds configs with `znc.conf` plus backups of older configs, a modules directory, moddata for global module settings and the per-user users tree. Modules and module data installed with the build live in `<prefix>/lib/znc` and `<prefix>/share/znc`, and the only loose file in the data dir is `znc.pem`.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. Project website
  4. README
  5. znc/znc on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/znc-znc.svg)](https://hysenlabs.com/projects/znc-znc)