nircc is a self-hosted IRC client that runs as a small Node.js server and serves a modern web UI to your browser. Because the server holds the IRC connection, you stay present in channels even when your browser is closed, and your full message history is always available the moment you reconnect.
- Always-on IRC connection — the server maintains the IRC socket; you receive everything even while offline
- Persistent message history — all channel and private-message history is stored locally in SQLite and loaded on demand
- Progressive Web App (PWA) — installable on desktop and mobile, works in standalone mode
- Multiple IRC server profiles — save, edit, and switch between servers from the settings panel
- TLS support — connect to servers over plain-text or TLS (default port 6697)
- Real-time updates — the browser UI stays live via WebSocket
- File uploads — attach files up to 50 MB; links are shared in chat and served directly by nircc
- Image previews — external images in chat can be proxied and shown inline (SSRF-safe, public IPs only, 8 MB limit)
- Web Push notifications — receive push notifications for new private messages (queries) on any subscribed device
- Reconnect with exponential backoff — configurable cap (2 – 256 seconds); reconnects automatically on disconnect
- Dark and light themes — toggle from the chat action menu; persisted across sessions
- Resizable sidebar — drag the sidebar edge to your preferred width; remembered per browser
- Font scaling —
Ctrl + scroll wheelto adjust text size - Byte counter — live 0/480 indicator enforces the IRC message byte limit before you send
- IRC slash commands — full command palette directly in the message input
- CTCP support — send and receive PING / VERSION / TIME; latency displayed for PING replies
- User context menu — right-click any nick in the user list for WHOIS, QUERY, Op/Voice/Deop/Devoice, Kick, and CTCP actions
- Node.js 18 or later
- npm (bundled with Node.js)
git clone <repo-url> nircc
cd nircc
npm installCopy or create conf.json in the project root. All fields are optional; sensible defaults apply when the file is absent.
{
"port": 3000,
"subfolder": "/",
"push": {
"vapidPublicKey": "...",
"vapidPrivateKey": "...",
"vapidSubject": "mailto:admin@example.com"
}
}| Key | Default | Description |
|---|---|---|
port |
3000 |
HTTP port nircc listens on. Overridden by the PORT environment variable. |
subfolder |
"/" |
Base URL path when nircc is served behind a reverse proxy under a subdirectory (e.g. "/irc/"). |
push.vapidPublicKey |
— | VAPID public key for Web Push. Leave empty to disable push notifications. |
push.vapidPrivateKey |
— | VAPID private key for Web Push. |
push.vapidSubject |
"mailto:admin@example.com" |
Contact URI included in VAPID tokens. |
npx web-push generate-vapid-keysPaste the output into conf.json to enable Web Push notifications.
npm startnircc starts and prints:
nircc listening on http://localhost:3000
Open that URL in your browser to use the client.
| Variable | Description |
|---|---|
PORT |
Overrides the port value in conf.json. |
| Flag | Description |
|---|---|
--no-console |
Disables the interactive server console (useful when running under a process manager). |
- Open the app in your browser.
- Click Connect in the sidebar (or the gear icon) to open Settings.
- Under Connection, add an IRC server using the Add button. Fill in the host, port, and whether to use TLS. Common public networks:
irc.libera.chat/ port6697/ TLS ✓irc.oftc.net/ port6697/ TLS ✓irc.freenode.net/ port6697/ TLS ✓
- Fill in your Nickname, Username, and Real name.
- Click Save, then Connect.
Your server profiles and identity settings are persisted on the nircc server and restored across sessions.
Type commands directly into the message input.
| Command | Description |
|---|---|
/join #channel |
Join a channel |
/part [#channel] [reason] |
Leave a channel (defaults to the active channel) |
/leave [#channel] [reason] |
Alias for /part |
/nick <new-nick> |
Change your nickname |
/query <nick> |
Open a private message conversation |
/whois <nick> |
Look up a user |
/topic [#channel] [new topic] |
View or set a channel topic |
/mode <target> [modes] [args] |
View or set channel/user modes |
/kick <#channel> <nick> [reason] |
Kick a user from a channel |
/ctcp <nick> <PING|VERSION|TIME> |
Send a CTCP request |
Settings are organised into three tabs in the Settings panel.
- IRC server profiles — add, edit, and delete saved servers. Each profile stores the hostname, port, TLS flag, and optional server password.
- Identity — nickname, username, real name.
- Client strings — optional VERSION reply string and quit message.
- Connect on Startup — automatically connect when the page loads.
- Reconnect on disconnection — re-establish the connection if it drops, using exponential backoff.
- Backoff cap — maximum delay between reconnect attempts (2 – 256 seconds; default 32 s).
- Notifications — enable or disable Web Push for new private messages globally, and toggle push per device.
- Indent wrapped chat lines — visually indent lines that wrap past the first line.
- Show Debug Messages — show raw-level debug output in conversations.
- Automatic file previews — show a preview widget for files you upload before sending the link.
- Auto-load image previews — automatically load and display external images inline (when disabled, a "Load preview" button is shown instead).
Click the 📎 (paperclip) button, or drag and drop a file onto the message input. nircc uploads the file to its own server (max 50 MB) and inserts the link into your message. Uploaded files are served at /uploads/<token>/<filename>.
Download counts per upload are tracked in real time and broadcast to all connected browser tabs.
When VAPID keys are configured in conf.json, the browser can subscribe to push notifications for new private messages (queries). Subscriptions survive browser restarts and work even when the tab is closed.
Push notifications can be toggled:
- Globally — via Settings → Options → Notifications
- Per conversation — via the ⋮ (chat actions) menu or right-clicking the conversation in the sidebar
nircc is designed to sit behind nginx, Caddy, or any other reverse proxy.
Example nginx snippet for serving nircc at /irc/:
location /irc/ {
proxy_pass http://127.0.0.1:3000/irc/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}Set "subfolder": "/irc/" in conf.json to match.
| Path | Contents |
|---|---|
data/history.db |
SQLite database — message history, settings, server profiles, push subscriptions |
data/uploads/ |
Uploaded files |
data/nircc-server.log |
Server log (when enabled) |
data/is git-ignored and will not be committed.
[Unit]
Description=nircc IRC client
After=network.target
[Service]
Type=simple
WorkingDirectory=/opt/nircc
ExecStart=/usr/bin/node index.js --no-console
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.targetSave to /etc/systemd/system/nircc.service, then:
sudo systemctl daemon-reload
sudo systemctl enable --now nircc| Shortcut | Action |
|---|---|
Enter |
Send message |
Shift + Enter |
Insert a newline |
Ctrl + Scroll |
Increase / decrease font size |
This section tracks how completely nircc implements the IRC protocol as used on IRCnet (RFC 1459 / RFC 2812) and related de-facto standards. It is intended as a working checklist for future development.
Legend: ✅ implemented ·
| Feature | Status | Notes |
|---|---|---|
| TCP connect (plain) | ✅ | |
| TLS connect | ✅ | Certificate validation is disabled (rejectUnauthorized: false) |
| TLS certificate verification | ❌ | Always trusts the server certificate |
| PASS (server password) | ✅ | |
| NICK (registration) | ✅ | |
| USER (registration) | ✅ | |
| QUIT (with message) | ✅ | Configurable quit message |
| PING / PONG (keepalive) | ✅ | Auto-handled |
| Automatic reconnect | ✅ | Exponential backoff, configurable cap |
| Connect on startup | ✅ | Optional setting |
| IRCv3 CAP negotiation | ❌ | No CAP LS / CAP REQ / CAP ACK |
| SASL authentication | ❌ |
| Numeric | Name | Status | Notes |
|---|---|---|---|
| 001 | RPL_WELCOME | ✅ | Triggers post-connect self-WHOIS |
| 002 | RPL_YOURHOST | ❌ | Received but silently ignored |
| 003 | RPL_CREATED | ❌ | Received but silently ignored |
| 004 | RPL_MYINFO | ❌ | Received but silently ignored |
| 005 | RPL_ISUPPORT (FEATURE) | ❌ | Not parsed; supported modes/prefixes are hardcoded |
| 375/372/376 | MOTD | ❌ | Not displayed to the user |
| 251–255 | LUSERS | ❌ | Not displayed |
| 265/266 | Local/global user count | ❌ | Not displayed |
| Feature | Status | Notes |
|---|---|---|
| NICK (change while connected) | ✅ | Promise-based with 10 s timeout |
| NICK change reflected in user lists | ✅ | All joined channels updated |
| 433 ERR_NICKNAMEINUSE | ✅ | Error surfaced to UI |
| 432 ERR_ERRONEUSNICKNAME | ✅ | |
| 436 ERR_NICKCOLLISION | ✅ | |
| 437 ERR_UNAVAILRESOURCE | ✅ | |
| Automatic nick recovery after reconnect | ❌ | Reconnects with original nick; no alt-nick or ghost/regain |
| MONITOR / WATCH (nick online tracking) | ❌ | |
| ISON / USERHOST | ❌ |
| Feature | Status | Notes |
|---|---|---|
| JOIN | ✅ | |
| PART (with reason) | ✅ | |
| KICK (with reason) | ✅ | |
| TOPIC (get and set) | ✅ | |
| PRIVMSG to channel | ✅ | |
| MODE (send arbitrary modes) | ✅ | Raw mode string forwarded to server |
| Channel mode display in chat | ✅ | Mode changes shown as status messages |
| User-list op/voice prefix tracking | ✅ | @ and + tracked via MODE |
| NAMES list (353/366) | ✅ | Full refresh on join |
| INVITE (send) | ❌ | No /invite command |
| INVITE (receive / display) | ❌ | Incoming INVITE not shown |
| LIST (channel browsing) | ❌ | No /list command or channel browser |
JOIN with key (+k channel) |
❌ | /join #channel key not parsed; key not forwarded |
| Ban list (367/368) | ❌ | MODE +b sent but ban-list numerics not processed |
| Ban exception list (346/347) | ❌ | |
| Invite exception list (348/349) | ❌ | |
Half-op prefix (%) |
Prefix preserved from NAMES but +h mode change not tracked |
|
Admin / owner prefixes (~ &) |
Prefix preserved from NAMES but mode changes not tracked | |
| Multi-prefix (several prefixes per nick) | Display-only; relies on server sending combined prefix in NAMES |
| Feature | Status | Notes |
|---|---|---|
| PRIVMSG (send, channel) | ✅ | |
| PRIVMSG (send, private query) | ✅ | |
| PRIVMSG (receive, channel) | ✅ | |
| PRIVMSG (receive, private query) | ✅ | |
| Outgoing message byte-limit enforcement | ✅ | 480-byte cap with live counter |
| Long-message truncation | Truncated to fit one line; no automatic splitting across multiple PRIVMSGs | |
| NOTICE (receive from server/users) | ❌ | Plain-text NOTICEs are silently discarded; only CTCP-encoded NOTICEs (replies) are handled |
| NOTICE (send) | ❌ | No /notice command |
/me — CTCP ACTION (send) |
❌ | |
/me — CTCP ACTION (receive/display) |
❌ | ACTION messages arrive as plain PRIVMSG; \x01ACTION …\x01 envelope is not stripped or styled |
mIRC color codes (\x03) |
❌ | Rendered as raw control characters |
IRC bold (\x02) |
❌ | |
IRC italic (\x1D) |
❌ | |
IRC underline (\x1F) |
❌ | |
IRC reverse (\x16) |
❌ | |
IRC strikethrough (\x1E) |
❌ | |
IRC monospace (\x11) |
❌ | |
IRC reset (\x0F) |
❌ |
| Feature | Status | Notes |
|---|---|---|
| AWAY (set/unset self) | ❌ | No /away command |
| Away status in WHOIS (301) | ✅ | Shown in WHOIS output |
| Away notification for other users | ❌ | AWAY messages from other users not tracked |
| 305/306 RPL_UNAWAY / RPL_NOWAWAY | ❌ | Not handled |
| Feature | Status | Notes |
|---|---|---|
| WHOIS (send and receive) | ✅ | Full result displayed in active conversation |
| 311 RPL_WHOISUSER | ✅ | |
| 312 RPL_WHOISSERVER | ✅ | |
| 313 RPL_WHOISOPERATOR | ✅ | |
| 317 RPL_WHOISIDLE | ✅ | Idle time and signon time |
| 318 RPL_ENDOFWHOIS | ✅ | |
| 319 RPL_WHOISCHANNELS | ✅ | |
| 301 RPL_AWAY (in WHOIS) | ✅ | |
| 307 RPL_USERIP (registered) | ✅ | |
| 330 RPL_WHOISACCOUNT | ✅ | |
| 671 (secure connection) | ✅ | |
| 378 (actual host) | ✅ | |
| 276 (certificate fingerprint) | ✅ | |
| 401 ERR_NOSUCHNICK | ✅ | |
| WHO / 352 RPL_WHOREPLY | ❌ | |
| WHOWAS / 314 RPL_WHOWASUSER | ❌ |
| Feature | Status | Notes |
|---|---|---|
| CTCP PING (auto-respond to incoming) | ✅ | |
| CTCP PING (send, with latency display) | ✅ | |
| CTCP VERSION (auto-respond) | ✅ | Configurable reply string |
| CTCP VERSION (send) | ✅ | |
| CTCP TIME (auto-respond) | ✅ | |
| CTCP TIME (send) | ✅ | |
CTCP ACTION (/me) |
❌ | Not sent or specially rendered on receive |
| CTCP CLIENTINFO | ❌ | |
| CTCP USERINFO | ❌ | |
| CTCP FINGER | ❌ | |
| CTCP SOURCE | ❌ | |
| DCC SEND / CHAT | ❌ | Out of scope (no direct connection support) |
| Feature | Status | Notes |
|---|---|---|
| OPER | ❌ | |
| KILL | ❌ | |
| WALLOPS / GLOBOPS | ❌ | |
| REHASH / RESTART / DIE | ❌ | |
| CONNECT / SQUIT | ❌ | |
| STATS | ❌ | |
| LINKS | ❌ | |
| TRACE | ❌ |
None of the IRCv3 extensions are currently negotiated via CAP. Implementing CAP LS / CAP REQ is the prerequisite for all items below.
| Extension | Status | Notes |
|---|---|---|
message-tags |
❌ | Tags on incoming lines are not parsed |
server-time |
❌ | Timestamps come from the nircc server clock, not the IRC server |
echo-message |
❌ | Self-echo is handled by a local guard, not the extension |
away-notify |
❌ | |
account-notify |
❌ | |
extended-join |
❌ | |
chghost |
❌ | |
batch |
❌ | |
labeled-response |
❌ | |
multi-prefix |
❌ | |
userhost-in-names |
❌ | |
MONITOR |
❌ | |
sasl |
❌ | |
msgid |
❌ | |
draft/pre-away |
❌ |
| Feature | Status | Notes |
|---|---|---|
| Ignore list | ❌ | No local message filtering |
| Highlight / mention detection | ❌ | Own nick is not highlighted in incoming messages |
| Sound / visual mention alerts | ❌ | |
| Channel auto-join on connect | ❌ | No saved channel list; user must /join manually each session |
| Conversation search / grep | ❌ | |
| Log export | ❌ |
Based on the gaps above, these are the most impactful items to tackle first:
- NOTICE display — plain-text server and user NOTICEs are completely silent right now; this breaks things like NickServ registration flows and server announcements.
- CTCP ACTION (
/me) — extremely common; both receive rendering and/mesend support. - IRC text formatting — strip or render
\x02bold,\x03colour,\x1Funderline,\x0Freset; required for many networks and bots. - MOTD display — servers send this immediately after connect and it often contains important rules/information.
- NOTICE send (
/notice) — needed for interacting with services on many networks. - Channel auto-join — save a list of channels to rejoin automatically after connect.
/awaycommand — set/unset away status./invitecommand — invite users to a channel.- IRCv3 CAP negotiation — unlocks
server-time,away-notify,account-notify,extended-join, and eventually SASL; foundational for many improvements. - SASL PLAIN / EXTERNAL — needed for modern network authentication (Libera.Chat, OFTC).
- Highlight / mention detection — notify when own nick appears in a channel message.
- Channel auto-rejoin on reconnect — currently all channels are lost on disconnect.
- 005 ISUPPORT parsing — replace hardcoded mode/prefix assumptions with server-advertised values.
- TLS certificate verification — currently all certificates are trusted without validation.
MIT