Server Configuration
Esta página aún no está disponible en tu idioma.
Every setting has three places it can live: the YAML config file, an environment variable, and a command-line flag. A flag you type wins over the environment, which wins over the file, which wins over the built-in default. A flag’s default never overrides the file.
For a service install, put everything in the file and let the unit be ExecStart=/usr/local/bin/woho -config /etc/woho/woho-server.yaml.
Configuration File
Section titled “Configuration File”/etc/woho/woho-server.yaml| Running | If the file is missing |
|---|---|
| Default path | Fine — flags, environment and defaults apply |
-config <path> | Error. A path you named must exist |
Creating it
Section titled “Creating it”config init writes a fully commented file with every setting: the defaults, plus whatever the environment and the flags you pass say.
woho config init \ -tls-cert /etc/woho/tunnel.crt -tls-key /etc/woho/tunnel.key \ -rp-id woho.example.com -rp-origin https://woho.example.comIt refuses to overwrite an existing file. -config - prints to stdout.
Schema
Section titled “Schema”The key is the flag name with underscores.
http: '0.0.0.0:8080' # admin panel listen addresstcp: '0.0.0.0:9000' # agent tunnel listen addressdb: '/var/lib/woho/woho.db' # SQLite file — absolutetls_cert: '/etc/woho/tunnel.crt' # tunnel certificate and key: both or neithertls_key: '/etc/woho/tunnel.key'tunnel_insecure: false # cleartext tunnel; exclusive with the pair aboverp_id: 'woho.example.com' # WebAuthn relying party: the panel's hostnamerp_origin: 'https://woho.example.com' # scheme://host[:port]; empty derives from httpproxy_domain: '' # wildcard parent domain for Web forwardsmetrics: '127.0.0.1:9090' # Prometheus address; '' disablestunnel_max_pending: 256 # unregistered tunnel connections, server-widetunnel_max_pending_per_ip: 32 # the same, per source addressretain_admin_activity: 90 # days; 0 keeps foreverretain_connection_history: 90retain_login_sessions: 90retain_sessions: 90record_terminals: false # record terminal output for replayretain_transcripts: 30 # days to keep recordingstranscript_max_mb: 10 # cap per recording, 1–100vacuum_free_percent: 25 # VACUUM after a purge at this much free spacesession_idle_timeout: 2h # 2h, 30m; 0 disablesproxy_idle_timeout: 4htrusted_proxy_cidrs: [] # reverse proxies whose X-Forwarded-For is believedstorage_dir: '/var/lib/woho/storage' # admins' server-side file area — absolutelog_level: 'info' # debug, info, warn, errorlog_format: 'json' # json or textThe file is strict. Each of these stops the server with an error naming the key, never a silent fallback:
- an unknown or duplicate key, a second YAML document
- a value of the wrong type, including a bare number where a duration is expected —
2is not two hours - a relative path for
db,tls_cert,tls_keyorstorage_dir - a listen address with no port, an
rp_originwith a path, aproxy_domainthat is a URL tls_cert/tls_keynext totunnel_insecure: true, one of the pair without the other, or neither- a negative count or duration,
vacuum_free_percentabove 100,transcript_max_mboutside 1–100, an invalid CIDR, an unknown log level or format
Applying a change
Section titled “Applying a change”The file is read once, at startup.
vi /etc/woho/woho-server.yamlwoho config check -config /etc/woho/woho-server.yamlsystemctl restart woho-serverconfig check resolves the settings exactly as a start would and prints each one with where its value came from. It exits 1 with the message the server would have logged, so a typo is caught before the restart.
Permissions
Section titled “Permissions”The file holds no secret — tls_key is a path. config init writes it 0644 owned by root; the server warns if it is writable by group or others.
| Flag | Default | Description |
|---|---|---|
-config | /etc/woho/woho-server.yaml when it exists | YAML config file |
-http | 0.0.0.0:8080 | Admin panel bind address |
-tcp | 0.0.0.0:9000 | Agent tunnel bind address |
-db | woho.db | SQLite database file |
-storage-dir | storage | Admins’ server-side file area; each admin gets <dir>/files/<username> |
Agent tunnel transport
Section titled “Agent tunnel transport”The tunnel has no default transport; the server will not start without one of these.
| Flag | Default | Description |
|---|---|---|
-tls-cert | env WOHO_TLS_CERT | PEM certificate for the tunnel |
-tls-key | env WOHO_TLS_KEY | PEM private key |
-tunnel-insecure | false | Cleartext tunnel. Agents must use -insecure |
On startup the server logs the certificate’s SPKI pin; agents need it only when the certificate is not publicly trusted. Certificates are read once — restart after renewing.
WebAuthn
Section titled “WebAuthn”| Flag | Default | Description |
|---|---|---|
-rp-id | localhost | Relying party ID — the panel’s hostname, no scheme or port |
-rp-origin | derived from -http | Full origin, https://woho.example.com |
-rp-id must be a domain name that matches, or is a parent of, the origin’s host. Passkeys need HTTPS, and stop working if rp_id changes.
Web forwards
Section titled “Web forwards”| Flag | Default | Description |
|---|---|---|
-proxy-domain | none | Parent domain serving forwarded content, one hostname per forward — below |
-proxy-idle-timeout | 4h | Close a forward with no traffic for this long; 0 disables |
Tunnel connection caps
Section titled “Tunnel connection caps”| Flag | Default | Description |
|---|---|---|
-tunnel-max-pending | 256 | Tunnel connections that have not yet registered, server-wide; 0 disables |
-tunnel-max-pending-per-ip | 32 | The same, per source address |
A connection counts only from accept until the agent registers. The caps bound registrations in flight, not the fleet: a thousand connected agents hold no slots. Refusals are counted as woho_tcp_connections_rejected_total{reason="global"|"per_ip"}.
Data retention
Section titled “Data retention”| Flag | Default | Table |
|---|---|---|
-retain-admin-activity | 90 days | Admin actions — the audit trail |
-retain-connection-history | 90 days | Agent connection attempts |
-retain-login-sessions | 90 days | Admin logins |
-retain-sessions | 90 days | Ended agent sessions. A live session is never purged |
-retain-transcripts | 30 days | Terminal recordings |
-vacuum-free-percent | 25 | Compact the database after the daily purge when at least this share of it is free (and 8 MiB reclaimable); 0 never |
0 on any -retain-* flag keeps forever. The purge runs at startup and every 24 hours. A vacuum locks the database while it rewrites the file and needs free disk roughly equal to its size; the threshold is sized so it fires once, on the first upgrade of a long-running install, and then effectively never.
Terminal recording
Section titled “Terminal recording”| Flag | Default | Description |
|---|---|---|
-record-terminals | false | Record every terminal’s output for replay from the machine’s timeline |
-retain-transcripts | 30 | Days to keep recordings; 0 forever |
-transcript-max-mb | 10 | Largest recording, 1–100 MB. Past it the rest is not stored and the recording is marked truncated |
Sessions and logging
Section titled “Sessions and logging”| Flag | Default | Description |
|---|---|---|
-session-idle-timeout | 2h | Log an admin out after this much inactivity; 0 disables. Terminal traffic counts as activity. The absolute 24-hour lifetime applies regardless |
-trusted-proxy-cidrs | none | Comma-separated CIDRs of reverse proxies whose X-Forwarded-For / X-Real-IP are believed. Empty ignores the headers |
-log-level | info | debug, info, warn, error |
-log-format | json | json or text |
Metrics
Section titled “Metrics”| Flag | Default | Description |
|---|---|---|
-metrics | 127.0.0.1:9090 | Prometheus endpoint, or "" to disable |
Subcommands
Section titled “Subcommands”| Command | Description |
|---|---|
woho config init [-config PATH] [flags] | Write a commented config file. Refuses to overwrite; -config - prints to stdout |
woho config check [-config PATH] [flags] | Validate everything, load the certificate, print each setting with its source and the SPKI pin |
woho -version | Version, build time, commit |
Examples
Section titled “Examples”# Production, on the config filewoho -config /etc/woho/woho-server.yaml
# One setting for one run, on top of the filewoho -config /etc/woho/woho-server.yaml -log-level debug
# Local trialwoho -http :8080 -tcp :9000 -db /var/lib/woho/woho.db -tunnel-insecureEnvironment Variables
Section titled “Environment Variables”A typed flag beats them; they beat the file.
| Variable | Flag |
|---|---|
WOHO_TLS_CERT, WOHO_TLS_KEY | -tls-cert, -tls-key |
WOHO_TUNNEL_INSECURE | -tunnel-insecure (only the exact value true) |
WOHO_PROXY_DOMAIN | -proxy-domain |
WOHO_PROXY_IDLE_TIMEOUT | -proxy-idle-timeout |
METRICS_ADDR | -metrics |
WOHO_TUNNEL_MAX_PENDING, WOHO_TUNNEL_MAX_PENDING_PER_IP | -tunnel-max-pending, -tunnel-max-pending-per-ip |
WOHO_RETAIN_ADMIN_ACTIVITY_DAYS | -retain-admin-activity |
WOHO_RETAIN_CONNECTION_HISTORY_DAYS | -retain-connection-history |
WOHO_RETAIN_LOGIN_SESSIONS_DAYS | -retain-login-sessions |
WOHO_RETAIN_SESSIONS_DAYS | -retain-sessions |
WOHO_RECORD_TERMINALS | -record-terminals (only true) |
WOHO_RETAIN_TRANSCRIPTS_DAYS | -retain-transcripts |
WOHO_TRANSCRIPT_MAX_MB | -transcript-max-mb |
WOHO_VACUUM_FREE_PERCENT | -vacuum-free-percent |
WOHO_SESSION_IDLE_TIMEOUT | -session-idle-timeout |
TRUSTED_PROXY_CIDRS | -trusted-proxy-cidrs |
WOHO_STORAGE_DIR | -storage-dir |
DEV_MODE | no flag — see below |
An empty environment value means unset. A file value shadowed by an environment variable is named in the startup log and by config check.
DEV_MODE=true is for development only: unbundled assets, cookies over HTTP, and debug / text as the logging defaults.
Agent Tunnel TLS
Section titled “Agent Tunnel TLS”The admin panel’s HTTPS is handled by the reverse proxy; the tunnel on port 9000 is configured in WoHo itself.
Use a certificate that matches the hostname agents connect to — usually the panel’s own:
tls_cert: '/etc/letsencrypt/live/woho.example.com/fullchain.pem'tls_key: '/etc/letsencrypt/live/woho.example.com/privkey.pem'The woho user must be able to read the key. If it cannot, copy the pair:
install -o woho -g woho -m 0400 privkey.pem /etc/woho/tunnel.keyinstall -o woho -g woho -m 0444 fullchain.pem /etc/woho/tunnel.crtAgents then need no TLS option: they verify against the system root store. For a server without public DNS, generate a self-signed certificate and let agents pin it:
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes -days 3650 \ -keyout /etc/woho/tunnel.key -out /etc/woho/tunnel.crt \ -subj "/CN=woho.internal" -addext "subjectAltName=DNS:woho.internal,IP:10.0.0.5"The server logs the SPKI pin at startup; agents pass it with -pin. Pinning checks the key only, so the certificate’s expiry does not matter. -pin takes several comma-separated values, so a key can be rotated with no outage: add the new pin on every agent, switch the server, remove the old one.
Confirm from outside:
openssl s_client -connect woho.example.com:9000 -tls1_3 </dev/nullWeb Proxy Domain
Section titled “Web Proxy Domain”Without proxy_domain, Web tabs are served from /proxy/ on the panel origin inside a sandbox with an opaque origin — isolated from the panel, but also unable to use their own cookies or storage. With it, every forward gets its own hostname:
https://<forward-id>.proxy.woho.example.com/It must be hostnames, not ports: cookies are scoped by host and ignore the port, so a second port on the panel’s hostname would receive the panel’s session cookie.
You need:
- A wildcard DNS record —
*.proxy.woho.example.compointing at the server. - A certificate covering the wildcard — a
*.proxy.woho.example.comSAN, which means DNS-01 validation. - The reverse proxy passing both names to the same WoHo port with the original
Hostintact — there is no second listener.
server { server_name woho.example.com *.proxy.woho.example.com; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; }}Forward hostnames answer 404 for /api/…, and the panel host answers 404 for /proxy/…. proxy_domain must not cover the panel’s own hostname.
Database
Section titled “Database”SQLite, created on first start with migrations applied and the bootstrap admin / admin account seeded. The file and its WAL sidecars are held at mode 0600.
# Backup while the server runssqlite3 /var/lib/woho/woho.db ".backup /backup/woho-$(date +%Y%m%d).db"
# Integritysqlite3 /var/lib/woho/woho.db "PRAGMA integrity_check;"Migrations run forward only: back up before upgrading if you may need to roll back.
Reverse Proxy (nginx)
Section titled “Reverse Proxy (nginx)”Preserves WebSockets, large file transfers, and the original host.
map $http_upgrade $connection_upgrade { default upgrade; '' close;}
upstream woho_http { server 127.0.0.1:8080;}
server { listen 443 ssl; server_name woho.example.com;
ssl_certificate /etc/letsencrypt/live/woho.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/woho.example.com/privkey.pem;
client_max_body_size 512m; client_body_timeout 180s; send_timeout 180s;
proxy_http_version 1.1; proxy_set_header Host $http_host; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;
location = /ws { proxy_pass http://woho_http; proxy_read_timeout 3600s; proxy_send_timeout 3600s; }
location = /api/files/client-upload { proxy_pass http://woho_http; proxy_request_buffering off; proxy_read_timeout 3600s; proxy_send_timeout 3600s; }
location / { proxy_pass http://woho_http; proxy_read_timeout 360s; proxy_send_timeout 360s; proxy_buffering off; proxy_request_buffering off; }}Then in the config file:
trusted_proxy_cidrs: ['127.0.0.1/32'] # add '::1/128' if nginx connects over IPv6rp_id: 'woho.example.com'rp_origin: 'https://woho.example.com'With trusted CIDRs set, the login rate limit and History use the forwarded address; X-Forwarded-For is read from the right, taking the first address that is not a trusted proxy. The agent tunnel port 9000 stays directly reachable; it does not go through an HTTP proxy.
Security Checklist
Section titled “Security Checklist”- Change the
admin/adminpassword on first login, before the panel is reachable from anywhere untrusted - Delete the bootstrap
adminonce you have a personal account - Tunnel TLS with
tls_cert/tls_key;tunnel_insecureonly for a local trial - HTTPS for the panel, with
trusted_proxy_cidrs,rp_idandrp_originset -
proxy_domainwith wildcard DNS and certificate if admins use Web tabs - Keep Require Authentication Key on
- A passkey for every admin
-
metricson loopback, or'' - Port
8080reachable only through the reverse proxy; port9000open to the managed machines - Decide knowingly about
record_terminals