End-to-end encrypted ephemeral chat running on a VPS with WebSockets under PM2.
| ▢index.html | 37.2 KB | 16 hr ago |
| ▢nulledroom.nginx.conf | 1 KB | 16 hr ago |
| ▢package.json | 288 B | 16 hr ago |
| ▢server.js | 7.9 KB | 16 hr ago |
NulledRoom
E2EE ephemeral chat. No logs. No history. No trace.
A self-hosted, end-to-end encrypted chat room with a terminal aesthetic. Messages are encrypted in the browser before they hit the wire. The server never sees plaintext. No accounts, no logs, no message history — when the room is gone, it's gone.
What it is
NulledRoom is a WebSocket-based chat app where:
- All messages are encrypted client-side with AES-GCM-256 before transmission
- The room ID is the key — only people who know the room ID can derive the encryption key
- Nothing is persisted — all state is in-memory; when the server restarts or a room empties, everything disappears
- No accounts — users get random hacker handles (null_socket_42, ghost_daemon_17, etc.)
- Images are supported — compressed client-side, encrypted, sent as encrypted blobs
- The URL never changes — room ID is never in the URL, so browser history reveals nothing
How E2EE works
The room ID (e.g. XK7P2Q) is used as the key material. When a user creates or joins a room, the browser runs:
PBKDF2(roomId, salt="nulledroom-salt-v1", iterations=100000, hash=SHA-256) → AES-GCM-256 key
All messages are encrypted with a random 12-byte IV before sending, and the server only ever sees an opaque base64 blob. Decryption happens client-side on receipt. If someone intercepts the WebSocket traffic, they see nothing useful without the room ID.
Tech stack
Layer | What
Backend | Node.js + ws (WebSocket library)
Frontend | Vanilla JS + Web Crypto API, single HTML file
Proxy | Nginx (SSL termination + WebSocket forwarding)
Process manager | PM2
Fonts | VT323 + Share Tech Mono (Google Fonts)
Hosting | Hetzner CPX11 VPS (Ubuntu 24.04)
Domain/SSL | Porkbun + Let's Encrypt / Certbot
Features
- Create room with configurable options:
- Inactivity timeout: 5 min to 24 hours (idle users get kicked)
- Nuke on host leave: if enabled, all users are disconnected when the creator leaves
- Join room with a 6-character alphanumeric code
- Room ID banner shown large and in-your-face after creation (easy to read on mobile)
- Image attachments: client-side canvas compression (max 20MB input, targets ~1.4MB after compression), E2EE before sending, click to expand in lightbox
- Silent guest reconnect: if a guest's connection drops (e.g. phone backgrounds the browser), they silently rejoin up to 8 times without spamming "[name] disconnected" to the room
- Nuke button for room creator — instantly destroys the room and boots everyone with a "NUKED" overlay
- Mobile-responsive terminal/CRT aesthetic (VT323 + Share Tech Mono, green-on-black, scanline overlay)
- Online users panel (hidden on small screens)
File structure
nulledroom/
├── server.js # Node.js WebSocket server
├── package.json # Single dependency: ws
├── index.html # Entire frontend (one file)
└── nulledroom.nginx.conf # Nginx config (for reference)
On the VPS:
/var/www/nulledroom/index.html # Frontend served by Nginx
/opt/nulledroom/server.js # Backend (wherever you put it)
/etc/nginx/sites-available/nulledroom # Nginx config
Prerequisites
- A VPS running Ubuntu 22.04 or 24.04 (Hetzner CPX11 works fine)
- A domain pointing to your VPS IP (A record)
- Node.js 18+ installed
- PM2 installed globally
- Nginx installed
- Certbot installed
Installation / Deploy from scratch
1. Server setup
bash
# Update system
sudo apt update && sudo apt upgrade -y
# Install Node.js (v20 LTS)
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
# Install PM2
sudo npm install -g pm2
# Install Nginx
sudo apt install -y nginx
# Install Certbot
sudo apt install -y certbot python3-certbot-nginx
2. Deploy backend
bash
# Create app directory
mkdir -p /opt/nulledroom
cd /opt/nulledroom
# Upload server.js and package.json here (scp, sftp, git clone, whatever)
# Then install deps:
npm install
# Start with PM2
pm2 start server.js --name nulledroom
pm2 save
pm2 startup # follow the printed command to make it survive reboots
3. Deploy frontend
bash
# Create web root
sudo mkdir -p /var/www/nulledroom
# Upload index.html here
sudo cp /path/to/index.html /var/www/nulledroom/index.html
4. Configure Nginx
bash
# Copy the nginx config
sudo cp /path/to/nulledroom.nginx.conf /etc/nginx/sites-available/nulledroom
# Enable it
sudo ln -s /etc/nginx/sites-available/nulledroom /etc/nginx/sites-enabled/nulledroom
# Remove default site if it's there
sudo rm -f /etc/nginx/sites-enabled/default
# Test config
sudo nginx -t
# Reload
sudo systemctl reload nginx
5. Get SSL cert
bash
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
Follow the prompts. Certbot will automatically fill in the cert paths in your Nginx config.
6. Verify
bash
# Check PM2 status
pm2 status
# Check logs
pm2 logs nulledroom
# Check Nginx
sudo systemctl status nginx
Open your domain in a browser. You should see the NulledRoom boot screen.
Updating files
Update the frontend (index.html)
bash
# From your local machine, SCP the file up:
scp index.html user@yourserver.com:/var/www/nulledroom/index.html
No restart needed — Nginx serves the file directly.
Update the backend (server.js)
bash
# SCP to the server
scp server.js user@yourserver.com:/opt/nulledroom/server.js
# SSH into the server and restart PM2
ssh user@yourserver.com
pm2 restart nulledroom
pm2 logs nulledroom # verify it started clean
PM2 cheat sheet
bash
pm2 status # see running processes
pm2 restart nulledroom # restart after server.js update
pm2 logs nulledroom # tail logs
pm2 logs nulledroom --lines 100 # last 100 lines
pm2 stop nulledroom # stop (doesn't delete)
pm2 delete nulledroom # remove from PM2
pm2 save # save current process list for auto-start
Nginx cheat sheet
bash
sudo nginx -t # test config for syntax errors
sudo systemctl reload nginx # apply config changes (no downtime)
sudo systemctl restart nginx # full restart
sudo systemctl status nginx # check status
Notes
- The server runs on port 3000 locally. Nginx proxies /ws to it over WebSocket and serves the frontend on 443.
- Room IDs are 6 characters, alphanumeric (no ambiguous chars like 0/O, 1/I). Generated client-side.
- Images are compressed to max 1920px on the longest side and run through quality reduction until under ~1.4MB before encryption. Canvas JPEG compression is used.
- The uint8ToBase64 chunked encoding function is critical — using btoa(String.fromCharCode(...largeArray)) blows the call stack on large images. The chunked version processes 8192 bytes at a time.
- Guests (non-creators) silently reconnect on WebSocket drop (up to 8 attempts, 2 second intervals). Hosts do not auto-reconnect — their disconnect is treated as intentional.
- There is zero persistence. If the server process dies, all rooms are gone. That's by design.
License
MIT
Download Nulled Room
The code is free and always will be. One email address and the ZIP is yours.
Logged so I know what people use. Nothing is ever sent to you unless you ask.
nulled-room.zip · 15.9 KB
Downloads currently need an account. Browsing the source stays open to everyone.
Download started
Check your browser's downloads if you don't see it.