############################################################################### # echoIRCd configuration # # Syntax: section { field value; field value; ... } # * one field per `;` * comments: # // and /* ... */ # * booleans are yes / no * quote values with spaces or #, e.g. "a b" # * repeat a block (oper, listen, link) or a field (alias, motd) as needed # # Every option below is shown with its BUILT-IN DEFAULT; uncomment a line only to # change it. Nothing is hardcoded — all values are read at runtime and most apply # again on `./echoircd rehash` (SIGHUP) without a restart. # # Copy this file to `echoircd.conf` and fill in your own values. NOTE: # echoircd.conf is gitignored because it holds secrets (oper password, cloak key, # link password). Never commit your real config. # # (The legacy flat `key = value` format is still accepted; a top-level `name {` # selects this block format.) ############################################################################### # ═══ server identity ═════════════════════════════════════════════════════════ server { name "irc.example.net"; # this server's unique name on the network network "ExampleNet"; # network name shown to clients sid "0AA"; # 3-char server id for S2S (digit + 2 alnum) description "echoIRCd server"; # shown in LINKS / WHOIS 312 # Write this server's PID here on boot so `echoircd rehash` (and `kill -HUP`) # can find and signal it to reload config in place. Omit to disable. pidfile "echoircd.pid"; } # ═══ listeners ═══════════════════════════════════════════════════════════════ # Repeatable. `ip "*"` (or "[::]") binds IPv4+IPv6 at once (dual-stack); an IPv4 # client on it is normalized back to its real v4 address. type: client (default) # | server (S2S) ; tls yes = direct TLS ; wss yes = WebSocket-over-TLS. listen { ip "0.0.0.0"; port 6667; } # plaintext clients listen { ip "0.0.0.0"; port 6697; tls yes; } # TLS clients listen { ip "0.0.0.0"; port 7000; type server; } # server-to-server links # listen { ip "[::]"; port 6667; } # dual-stack on one line # listen { ip "0.0.0.0"; port 7799; wss yes; } # wss:// (browser IRC clients) # listen { ip "127.0.0.1"; port 8097; ws yes; } # ws:// (plaintext WebSocket) # ═══ TLS ═════════════════════════════════════════════════════════════════════ # Generate a cert/key first, e.g.: # openssl req -x509 -newkey rsa:2048 -keyout tls/key.pem -out tls/cert.pem \ # -days 3650 -nodes -subj "/CN=irc.example.net" # The cert is re-read on REHASH, so a renewed cert applies without a restart. tls { cert "./tls/cert.pem"; key "./tls/key.pem"; # backend openssl; # openssl (default) | rustls (pure-Rust, no system OpenSSL) # sni "irc.example.net ./tls/example.crt ./tls/example.key"; # per-host cert (repeatable) } # ═══ MOTD ════════════════════════════════════════════════════════════════════ motd { "Welcome to echoIRCd."; "Edit the MOTD in your echoircd.conf."; } # opermotd { "Welcome to the staff team."; } # shown to opers via /OPERMOTD # ═══ operators ═══════════════════════════════════════════════════════════════ # The password accepts plaintext, sha256:, pbkdf2:… or a bcrypt $2b$ hash. # Generate a bcrypt hash with: printf '%s' 'yourpassword' | ./echoircd mkpasswd # Add fingerprint "" to also (or only, with no password) require the # client's TLS certificate. A block with neither password nor fingerprint is # refused (it would let anyone oper up). type references an opertype below. oper { name "admin"; password "CHANGE_THIS_PASSWORD"; type netadmin; # fingerprint ""; # require this TLS client cert too (2FA) # level 0; # operlevel (KILL protection tiers) } # Oper types (optional). No type = full access. A type is a named role (the WHOIS # "is a ") built from reusable capability classes; five ship built in: # helpop, globop, admin, servadmin, netadmin. Running a command your type doesn't # grant is refused; its modes/snomasks/vhost are applied on oper-up. # class { name "helpdesk"; commands "CHECK"; snomasks "c"; } # privs "..." # opertype { name "helpdesk"; classes "helpdesk"; modes "+ih"; title "Help_Desk"; level 15; } # ═══ server-to-server linking ════════════════════════════════════════════════ # The password is a shared secret. services yes marks the peer as a U-lined # services server (also handles SASL if it is the sasl_server below). # link { # name "peer.example.net"; # ip 203.0.113.5; # port 7000; # password "CHANGE_THIS_LINK_SECRET"; # autoconnect yes; # dial it on boot / after a netsplit (default no) # services no; # yes = U-lined services server # } # The linked server that handles SASL (client AUTHENTICATE is relayed to it). # Unset = SASL disabled. SASL EXTERNAL also needs the client on TLS with a client # cert (its fingerprint is forwarded to services). # services { sasl_server "services.example.net"; } # ═══ WEBIRC gateways ═════════════════════════════════════════════════════════ # Trusted web gateways (CGI:IRC / kiwiirc) send WEBIRC to declare the real # client's host+ip. mask restricts which source IP may use the password. # webirc { password "CHANGE_THIS_WEBIRC_SECRET"; name "mygateway"; mask "203.0.113.9"; } # ═══ host cloaking (+x) ══════════════════════════════════════════════════════ cloak { # Long random hex secret; keep it private. Changing it re-cloaks everyone. key "CHANGE_THIS_TO_A_LONG_RANDOM_HEX_STRING"; # method — how +x cloaks are built (repeatable/ordered; first match wins). # Default = hmac-sha256. Methods: hmac-sha256 (keyed subnet-preserving host # hash) | account (<account_prefix>/<account>) | fingerprint # (<cert_prefix>/<cert hash>) | static (everyone shows static_host). # method account; # method hmac-sha256; # account_prefix "account"; # default: account # cert_prefix "cert"; # default: cert # static_host "users.example.net"; } # ═══ DNS / ident ═════════════════════════════════════════════════════════════ dns { # reverse-DNS on connect (the "Looking up your hostname..." notices). resolve_hosts yes; # yes (default) resolves + reports; no = bare IP # whether a resolved name is used in the hostmask (only if resolve_hosts=yes). use_resolved_host yes; # yes (default) shows the domain; no keeps the IP # ident (RFC1413): off by default; a connect class can also enable it. # useident yes; # look up every client's ident (adds connect latency) # requireident yes; # refuse clients whose ident can't be confirmed # ident_timeout 5; # seconds to wait for the ident reply } # ═══ DNS blocklists (DNSBL) ══════════════════════════════════════════════════ # Repeatable. On a listing, dnsbl_action decides: mark (notice only, default) | # kill (disconnect) | kline/gline/zline (ban for dnsbl_duration + disconnect). # dnsbl { # dnsbl "dnsbl.dronebl.org"; # dnsbl "rbl.efnetrbl.org"; # dnsbl_action "mark"; # dnsbl_reason "Your host is listed in a DNS blocklist"; # dnsbl_duration 86400; # ban seconds for kline/gline/zline (default 1 day) # } # Per-blocklist form (one value string; overrides the globals): # dnsbl { dnsbl "domain=torexit.dan.me.uk name=TorExit action=zline duration=1w reason=Tor-not-allowed"; } # ═══ limits (advertised in ISUPPORT and enforced) ════════════════════════════ limits { # maxnick 30; # nick length (NICKLEN) # maxchannel 50; # channels a user may join (CHANLIMIT) # maxbans 100; # ban-list entries per channel (MAXLIST) # maxinvites 100; # pending invites tracked per user # modes 20; # mode changes per MODE line (MODES) # max_line 16384; # max bytes in one line / receive queue (16 KiB) # max_sendq 1048576; # queued output before a slow client is dropped (1 MiB) # whowas_maxentries 256; # historical nick records retained # maxwatch 128; # WATCH entries per user # maxmonitor 128; # MONITOR entries per user # maxsilence 32; # SILENCE entries per user # maxaccept 64; # /ACCEPT (caller-id) entries per user # maxsignore 64; # server-ignore entries per user # metadata_maxkeys 32; # IRCv3 METADATA keys per target # metadata_maxvalue 512; # METADATA value length # multiline_maxbytes 4096; # draft/multiline: max bytes advertised + enforced # multiline_maxlines 24; # draft/multiline: max lines # chathistory_limit 256; # CHATHISTORY messages kept per conversation # chathistory_maxage 604800; # max age (secs) a client may request (7 days) # dccallow_maxentries 20; # /DCCALLOW list entries per user # http_max_concurrent 32; # in-flight outbound HTTP requests (API modules) } # ═══ timeouts (seconds) ══════════════════════════════════════════════════════ timeouts { # registration_timeout 60; # drop clients that never register (NICK+USER) # ping_frequency 90; # send a PING after this much idle time # ping_timeout 60; # then drop if no PONG within this much longer # slow_command_ms 200; # snotice when one core event takes >= this (0=off) } # ═══ flood control ═══════════════════════════════════════════════════════════ flood { # flood_messages 8; # messages allowed per window before dropping (opers exempt) # flood_seconds 4; # the flood window # joinflood_duration 60; # +j (join flood) default window # nickflood_duration 60; # +F (nick flood) default window # connflood "5 10"; # refuse > N connections per S seconds from one IP # --- blockamsg: block mass /amsg and /ame --- # blockamsg yes; # blockamsg_delay 3; # same text to a different target list within N s = block # blockamsg_action block; # block | kill | gline | kline | zline # blockamsg_duration 900; # ban seconds for a *line action } # ═══ connection policy ═══════════════════════════════════════════════════════ connections { # --- conn_waitpong: hold registration until the client echoes a PING cookie # (filters bots that never PONG; real clients auto-reply). --- # conn_waitpong yes; # conn_waitpong_killonbadreply yes; # drop on wrong pong (default: keep waiting) # conn_waitpong_exempt_localhost4 yes; # exempt 127.0.0.0/8 # conn_waitpong_exempt_localhost6 yes; # exempt ::1 # --- connectban: z-line an IP range that opens too many connections --- # connectban yes; # connectban_threshold 10; # connections from a range before it's banned # connectban_duration 21600; # ban seconds (default 6h) # connectban_bootwait 120; # grace secs after startup (reconnect storm) # connectban_gcinterval 3600; # wipe the tally this often # connectban_ipv4cidr 32; # range width for IPv4 counting (/32) # connectban_ipv6cidr 128; # range width for IPv6 counting (/128) # connectban_banmessage "Too many connections from your address"; # connectban_exempt "10.0.0.0/8"; # never ban this glob/CIDR (repeatable) # --- accept-rate: drop connection-churn floods at the accept edge --- # accept_rate 0; # max NEW connections/sec per source IP (0 = off) # accept_burst 0; # instantaneous burst per IP (0 = same as accept_rate) # --- PROXY protocol: trust HAProxy/nginx PROXY header from these sources # (glob/CIDR, repeatable) so the real client IP is used. --- # proxy 127.0.0.1; # proxy 10.0.0.0/8; } # --- connectclass: per-class connection policy. Each value matches connecting # clients by IP/host mask (glob OR CIDR) + optional TLS/port; first match # wins, else the global limits apply. One quoted value string per class: # allow=<mask[,mask]> deny=yes parent=<name> requiressl=yes|trusted # password=<pw> hash=<algo> port=<p[,p]> localmax=<n> globalmax=<n> # limit=<n> maxchans=<n> pingfreq=<s> timeout=<s> modes=<+modes> # recvq=<bytes> softsendq=<bytes> hardsendq=<bytes> fakelag=no # penaltythreshold=<n> commandrate=<s> useident=yes requireident=yes # resolvehostnames=no maxconnwarn=yes # classes { # connectclass "trusted allow=10.0.0.0/8 maxchans=200 pingfreq=120 fakelag=no"; # connectclass "secure allow=* requiressl=yes password=sha256:<hex> hash=sha256"; # connectclass "vpn allow=* parent=trusted localmax=2 maxchans=20 modes=+ix"; # connectclass "banned allow=1.2.3.0/24 deny=yes"; # connectclass_required yes; # refuse clients that match no allow class (default no) # } # ═══ STS (Strict Transport Security) ═════════════════════════════════════════ # Tell CAP-302 clients on the plaintext port to upgrade to TLS and pin it. Off # unless sts_duration > 0. Only enable once TLS is solid — clients will then # refuse plaintext for the pinned duration. # sts { # sts_duration 2592000; # seconds clients should stick to TLS (0 = off) # sts_port 6697; # the TLS port to upgrade to # sts_preload no; # advertise preload eligibility # } # ═══ oper / staff behaviour ══════════════════════════════════════════════════ opers { # operprefix yes; # give every oper a `!` prefix (mode y, above owner) # ojoin yes; # /OJOIN <#chan> — join as network staff with `!` # ojoin_op yes; # also grant channel op on OJOIN (default yes) # oper_svslogin yes; # allow services to SVSLOGIN opers to an oper block # maphide yes; # hide LINKS / MAP from non-opers # hideservices yes; # hide U-lined services from MAP/LINKS/STATS for non-opers # rline_matchonnickchange yes; # re-check /RLINE regex bans on nick change # --- hidewhois: hide sensitive WHOIS lines from ordinary users --- # hidewhois yes; # hidewhois_opers yes; # opers still see everything (default yes) # hidewhois_selfview yes; # a user sees their own full WHOIS (default yes) # hidewhois_hide_server yes; # hide 312 server line (default yes) # hidewhois_hide_idle yes; # hide 317 idle (default yes) # hidewhois_hide_secure yes; # hide 671 secure-connection (default yes) } # --- hidemode / hidelist: restrict who sees a mode change / list mode, by rank # (owner|admin|op|halfop|voice). Opers/setter/links always see it. Repeatable. # channelvis { # hidemode "b op"; # hide ban changes below op # hidelist "b op"; # only ops may view the ban list # } # ═══ channels ════════════════════════════════════════════════════════════════ channels { # announce_channels yes; # snotice opers when a brand-new channel is created # chancreate yes; # (alias of announce_channels) # channames_deny ""; # forbid these chars in NEW channel names (control codes, etc.) # permchannels_database "permchannels.db"; # +P permanent channels (default <conf>.permchannels) # markread_database "markread.db"; # draft/read-marker persistence (default <conf>.markread) # --- restrictchans: only opers may CREATE channels (all may still join) --- # restrictchans yes; # restrictchan "#public-*"; # glob whitelist ordinary users may create (repeatable) # --- denychans: forbid joining channels matching a glob --- # badchan "#evil* reason=Off-limits redirect=#lobby allowopers=yes"; # goodchan "#evilgenius"; # whitelist back out of a broad badchan (repeatable) } # ═══ users: on-connect behaviour ═════════════════════════════════════════════ users { # connbanner "This network is for authorized users only."; # NOTICE at connect (repeatable) # conn_umodes "+ix"; # user modes auto-set on every client at connect (alias autoumodes) # autojoin "#lobby,#help"; # channels every client auto-joins (alias conn_join; repeatable) # seenicks yes; # snotice opers on every nick change (default no) # --- applied on successful OPER --- # opermodes "+ws"; # extra user modes set on oper-up (alias oper_umodes) # operjoin "#opers"; # channels an oper auto-joins on oper-up (repeatable) # --- self-service vhosts: /VHOST <user> <pass> sets your displayed host --- # vhost "alice s3cret alice.staff.example"; # (repeatable) } # ═══ account registration (IRCv3 draft/account-registration → HTTP API) ══════ # Bridges REGISTER / VERIFY to an HTTP backend (POST form-encoded + X-API-Key). # accounts { # account_registration yes; # master switch (default no) # acctregister_registerurl "https://accounts.example/register"; # required # acctregister_verifyurl "https://accounts.example/verify"; # required for VERIFY # acctregister_apikey "CHANGE_THIS_API_KEY"; # sent as X-API-Key # acctregister_autologin yes; # log in on successful register (default yes) # acctregister_beforeconnect yes; # allow REGISTER pre-registration (default yes) # acctregister_emailrequired yes; # require an email address (default yes) # acctregister_requiretls yes; # only over TLS (default yes) # acctregister_ratecount 3; # max register attempts per IP ... # acctregister_ratetime 3600; # ... per this many seconds # } # ═══ human-verification gates (anti-bot) ═════════════════════════════════════ # recaptcha: hand an unverified user a token+URL; they send CAPTCHA <token>. # cloudflare_challenge: same idea via VERIFYCHALLENGE <token>. JWT-only by default. # verification { # recaptcha yes; # recaptcha_secret "CHANGE_THIS_HS256_SECRET"; # signs the IP-bound token # recaptcha_url "https://example.org/captcha?token="; # recaptcha_issuer "echoIRCd"; # JWT issuer (default echoIRCd) # recaptcha_ttl 1800; # token lifetime secs (default 1800) # recaptcha_message "Please verify you are human:"; # recaptcha_whitelistports 6697; # listener ports exempt (repeatable) # cloudflare_challenge yes; # cloudflare_secret "CHANGE_THIS_HS256_SECRET"; # cloudflare_url "https://example.org/challenge?token="; # cloudflare_issuer "echoIRCd"; # cloudflare_ttl 1800; # cloudflare_message "Solve the challenge:"; # cloudflare_whitelistports 6697; # } # ═══ anti-spam / anti-drone ══════════════════════════════════════════════════ antiabuse { # --- antirandom: score random-looking nick/ident/realname (spam drones) --- # antirandom yes; # antirandom_threshold 10; # score at/above this acts (default 10) # antirandom_checkfull yes; # also score ident + realname (default yes) # antirandom_action kill; # block | kill | gline | kline | zline # antirandom_duration 86400; # ban seconds for a *line action # antirandom_reason "Random-looking connection rejected"; # antirandom_showfailed no; # snotice opers on a hit (default no) # --- hashident: replace ident with a stable opaque token per IP --- # hashident yes; # hashident_key "CHANGE_THIS_SECRET"; # HMAC key; makes the mapping unforgeable # --- solvemsg: unvouched users answer one arithmetic question before their # PMs are delivered (opers & logged-in accounts exempt). --- # solvemsg yes; # --- antimixedutf8: block spam mixing look-alike scripts within words --- # antimixedutf8 yes; # amu_threshold 8; # amu_minlen 10; # amu_action block; # block | kill | gline | kline | zline # amu_target both; # both | channel | private # amu_reason "Mixed-script spam blocked"; # --- +G censor words: badword "<find> [replacement]" (omit = block) --- # badword "examplebadword ***"; } # ═══ access restrictions ═════════════════════════════════════════════════════ restrictions { # restrictmsg yes; # only opers/services may be PM'd by ordinary users # disabled_commands "KNOCK"; # refuse these commands to ordinary users (repeatable) # restrictcommand "LIST connectdelay=60 exemptidentified=yes exempttls=no reason=Please-wait"; # --- securelist: delay /LIST for new connections (defeats list-spam bots) --- # securelist yes; # securelist_waittime 60; # seconds connected before /LIST works # securelist_exemptregistered yes; # logged-in users are exempt (default yes) # securelist_exception "*!*@trusted.example"; # exempt host glob (repeatable) # securelist_showmsg yes; # tell the early lister to wait (default yes) # securelist_fakechans 5; # size of the throwaway fake list shown # securelist_fakechanprefix "#"; # prefix for the fake channels # securelist_fakechantopic ""; # topic shown on the fake channels # --- autodrop: silently drop a pre-registration client that sends any of # these (HTTP scanners blurt GET/POST before NICK/USER): --- # autodrop_commands "GET POST HEAD CONNECT PUT DELETE OPTIONS TRACE PATCH"; } # ═══ reputation & security groups ════════════════════════════════════════════ # reputation: per-address scoring + y: score extban (MODE #c +b y:<100 / y:>500). # reputation { # reputation_database "reputation.db"; # default <conf>.reputation # reputation_minchanmembers 3; # only bump if in a channel this big # reputation_scorecap 10000; # max score # reputation_whois all; # all | opers | self | none # reputationexpire "2 1h"; # score<=2 decays after 1h (repeatable; * = any) # reputationexpire "* 90d"; # } # security groups — use as an extban: MODE #c +b g:<name>. criteria: public tls # insecure account unregistered oper exclude-oper bot webirc mask=<glob> # exclude=<glob> scoremin=<n> scoremax=<n>. # securitygroups { # securitygroup "trusted account tls public"; # securitygroup "newbies scoremax=10 public"; # } # ═══ logging & observability ═════════════════════════════════════════════════ logging { # --- syslog: mirror the server-notice / log stream to the system logger --- # syslog yes; # syslog_target "/dev/log"; # a Unix socket path, or host:port for UDP # syslog_facility daemon; # kern user mail daemon auth ... local0..local7 # syslog_tag echoircd; # --- log_json: append the log stream to a file as JSONL --- # log_json "/var/log/echoircd/events.jsonl"; # --- snoop_stderr: also echo the server-notice stream to stderr --- # snoop_stderr yes; # --- metrics: OpenMetrics/Prometheus scrape endpoint (plaintext HTTP GET); # bind privately or behind a proxy. --- # metrics_bind "127.0.0.1:9109"; # --- chanlog: mirror the oper snotice stream into a channel. Add snomask # letters after the channel to log only those (x x-lines, d dnsbl, # c connects, o oper, q quit, k kill …); none = everything. Repeatable. --- # chanlog "#snotices"; # everything # chanlog "#bans xdk"; # only x-lines, dnsbl hits, kills } # ═══ extra features / modules ════════════════════════════════════════════════ modules { # abbreviation yes; # a unique command prefix resolves to its command (WHOI→WHOIS) # --- command aliases: /NS ... → PRIVMSG <target> (services shortcuts) --- # alias "NS NickServ"; # alias "CS ChanServ"; # --- customprefix: reconfigure built-in prefix tiers, or add new ones --- # Built-ins (oper founder admin op halfop voice): bare token = sigil; # ranktoset/ranktounset = min rank to grant/revoke; depriv=no locks self-removal. # customprefix "op * ranktoset=admin ranktounset=admin depriv=no"; # New tier: letter+prefix required; rank default 1 (voice=10 halfop=20 op=30 # admin=40 founder=50 oper=60). # customprefix "helper letter=V prefix=? rank=25 ranktoset=op ranktounset=op"; # --- customtitle: /TITLE <name> <pass> grants a WHOIS title (+ optional vhost) --- # customtitle "staff s3cret staff.example.net Network Staff"; # --- randquote: greet each connecting user with a random line (repeatable) --- # randquote "The best way out is always through. — Robert Frost"; # --- showfile: serve a text file as its own command (read fresh each use) --- # showfile "RULES /etc/echoircd/rules.txt"; # --- filter (oper /FILTER): pattern engine for rules added after it --- # filter_engine glob; # glob (wildcards, default) | regex # --- geoip: MaxMind .mmdb country lookup. Enables +b G:<cc>, GEOIP, WHOIS country --- # geoip_database "/etc/echoircd/GeoLite2-Country.mmdb"; # --- network_icon: advertise a network icon via draft/ICON ISUPPORT --- # network_icon "https://example.org/icon.png"; # --- profilelink: a profile URL in WHOIS for logged-in users --- # profilelink_baseurl "https://example.org/profile/"; # --- relaymsg (draft/relaymsg): opers can /RELAYMSG under a spoofed nick (bridges) --- # relaymsg_separators "/"; # relaymsg_ident "relay"; # relaymsg_host "relay.example.com"; # default: the server name # --- extjwt: /EXTJWT issues a signed token proving IRC identity to a service --- # extjwt_secret "CHANGE_THIS_HS256_SECRET"; # required to enable # extjwt_duration 30; # token lifetime, seconds # extjwt_chunk 200; # token line-chunk size (bytes) when splitting # extjwt_service "myservice CHANGE_THIS_SERVICE_SECRET"; # per-service key (repeatable) # --- filehost: advertise a file-upload service + hand logged-in users a token --- # filehost_website "https://files.example.net"; # filehost_jwt_secret "CHANGE_THIS_HS256_SECRET"; # filehost_jwt_issuer "echoIRCd"; # filehost_requiressl yes; # only issue upload tokens to TLS users (default yes) # filehost_token_expiry 3600; # upload-token lifetime, secs # filehost_auth_message "Upload here:"; # --- dccallow: block unwanted DCC unless the recipient ran /DCCALLOW +<nick> --- # dccallow_blockfile "*.exe"; # (repeatable) # dccallow_blockchat yes; # also gate DCC CHAT # --- HTTP client (API modules): verify upstream TLS certificates --- # http_tls_verify yes; } # ═══ WebSocket tuning (only if a ws/wss listener is defined) ══════════════════ # websocket { # ws_origin "https://x.example"; # allowed Origin globs (repeatable); empty = any # ws_proxyranges "127.0.0.1"; # proxies whose X-Real-IP/XFF we trust (repeatable) # ws_trust_proxy no; # trust those headers from ANY peer (allows spoofing) # ws_allowmissingorigin yes; # allow clients that send no Origin header # ws_defaultmode text; # frame mode with no subprotocol: text|binary|reject # ws_nativeping yes; # liveness via WebSocket pings (no = IRC PING) # ws_handshake_timeout 10; # seconds to complete the HTTP Upgrade # ws_ping_interval 60; # seconds between WebSocket keepalive pings # ws_timeout 120; # drop after this many seconds of silence # }