Remote printing over WebSocket. Send print jobs from a server to a thermal printer (e.g. FC-588) at a remote office. The recipient double-clicks an exe, picks their printer, and it connects automatically. No port forwarding, no accounts, no configuration on the remote end.
Your Server (Linux VPS) Remote Windows PC
┌──────────────────┐ ┌───────────────────────┐
│ Print Server │◄──WebSocket────│ PrintAgent.exe │
│ (Bun/TypeScript) │ (outbound) │ (Go, ~6MB) │
│ │ │ │
│ POST /api/print │───job data────►│ → Windows Spooler │
│ GET /api/status │ │ → Thermal printer │
└──────────────────┘ └───────────────────────┘
The agent makes an outbound WebSocket connection to your server — no port forwarding or special config needed on the remote end.
cd server
bun install
bun run startexport PRINT_SERVER_PORT=9100 # default: 9100
export PRINT_API_SECRET=your-secret-here # for REST API authThe agent connects outbound to your server via WebSocket. You need to make the print server reachable on a domain.
Option A: Cloudflare Tunnel (recommended)
No reverse proxy needed — cloudflared connects directly to Cloudflare's edge.
-
Install cloudflared and authenticate:
cloudflared tunnel login cloudflared tunnel create print-server
-
Add the hostname to your tunnel config (
~/.cloudflared/config.yml):tunnel: <your-tunnel-id> credentials-file: /home/you/.cloudflared/<your-tunnel-id>.json ingress: - hostname: print.yourdomain.com service: http://localhost:9100 - service: http_status:404
-
Route DNS to the tunnel:
cloudflared tunnel route dns <your-tunnel-id> print.yourdomain.com
-
Run the tunnel (or set up as a systemd service):
cloudflared tunnel run <tunnel-name>
Agent connects to wss://print.yourdomain.com/ws — goes through port 443, works through any firewall. Cloudflare handles SSL and WebSocket proxying automatically.
Cloudflare has a 100-second WebSocket idle timeout. The agent pings every 30 seconds, so the connection stays alive automatically.
Option B: Cloudflare + reverse proxy
- Add a DNS A record:
print.yourdomain.com→ your VPS IP, Proxied (orange cloud on) - Add a reverse proxy on your VPS to forward to the print server:
Caddy (add to Caddyfile):
print.yourdomain.com {
reverse_proxy localhost:9100
}
Nginx:
server {
listen 443 ssl;
server_name print.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/print.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/print.yourdomain.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:9100;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 86400;
}
}Agent connects to wss://print.yourdomain.com/ws.
Option C: Direct (no Cloudflare)
Open the port and connect directly (unencrypted):
ufw allow 9100Agent connects to ws://your-vps-ip:9100/ws. Works but could be blocked by corporate firewalls that restrict non-standard ports.
pm2 start "bun run start" --name print-server --cwd /path/to/server
pm2 saveDownload PrintAgentSetup.exe from Releases and send it to the recipient.
Click through — all defaults are fine. The agent launches after install.
- Lists all installed printers — pick the target printer by number
- Asks for server URL — enter your WebSocket URL (e.g.
wss://print.yourdomain.com/ws) - Config saved — won't ask again
That's it. Starts automatically on boot, lives in the system tray.
- Green "CONNECTED - READY" = working, waiting for jobs
- Red "DISCONNECTED" = can't reach server, retrying automatically
- Scrolling log with timestamps — if something goes wrong, they can photograph the screen and send it to you
- "No printers found" → Make sure the printer is plugged in and turned on, restart the exe
- "DISCONNECTED" won't go away → Check internet connection. The exe retries automatically.
- Need to change printer or server → Delete
config.jsonnext to the exe (in Program Files) and restart
From any service on your server:
# Plain text (auto-wrapped in ESC/POS init + cut commands)
curl -X POST http://localhost:9100/api/print \
-H "Authorization: Bearer your-secret-here" \
-H "Content-Type: application/json" \
-d '{"content": "Hello from the server!\nThis prints on the remote printer."}'
# Raw ESC/POS (base64 encoded)
curl -X POST http://localhost:9100/api/print \
-H "Authorization: Bearer your-secret-here" \
-H "Content-Type: application/json" \
-d '{"raw_data": "G0AbaEhlbGxvIFdvcmxkCg=="}'
# Check agent status
curl http://localhost:9100/api/status \
-H "Authorization: Bearer your-secret-here"
# List recent jobs
curl http://localhost:9100/api/jobs \
-H "Authorization: Bearer your-secret-here"- Zero-config remote end — outbound WebSocket, no port forwarding needed
- Auto-reconnect — exponential backoff, survives network drops
- System tray — minimize to tray, auto-start on boot
- Verbose logging — large monospace text, photographable for remote debugging
- ESC/POS support — send raw thermal printer commands or plain text
- Job queue — jobs queue on the server if the agent is offline, sent when it reconnects
- Installer — Inno Setup, auto-start, clean uninstall
# Agent (Windows)
cd agent
go mod download
go build -ldflags="-H windowsgui -s -w" -o ../PrintAgent.exe .
# Installer (requires Inno Setup 6)
iscc installer.iss
# Server
cd server
bun run startRequires Go 1.21+ on Windows (uses Win32 APIs for printing and GUI).