OverviewFilesCommitsIssuesDocsReleasesPeers

7rnx-proxy commits / 91f4680

1.2.0 — emit KeepAliveTimeout 65 at server context
Debian ships KeepAliveTimeout 5, which closes the connection between one page view and the next click. Every client of this proxy is remote — ~95ms RTT to the VPS, plus ~110ms more over the VPN to a home backend — so each reconnect re-pays a TCP+TLS handshake. Measured on /git/repos: fresh connection TTFB 427ms vs 210ms on a reused one. The handshake was HALF of a cold load, and users experienced it as the page "randomly needing to warm up again" after idling more than 5 seconds. generateVhosts now emits the override at TOP LEVEL of sites-enabled/7rnx-proxy-vhosts.conf, above the first <VirtualHost>, so it covers the apex path-routed site (which serves /git) rather than only the host-routed vhosts. apache2.conf defines it at ~line 124 and includes sites-enabled/ at ~238, so this later definition wins. Verified in prod after deploy: one TLS connection served two full 200 responses across a 12s idle gap, which the old timeout of 5 made impossible. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
91f4680aded2523af26402ad387f031ba6931b4e
by plankatron <fleeky@gmail.com> · 2026-08-11T15:04:14.000Z

Changed files (3) +41 -1

lib/config-generator.js +17 -0
……@@ -23,6 +23,23 @@ export function generateVhosts (registry) {
2323 lines.push('# TLS is supplied by mod_md for any *.7rnx.net ServerName.')
2424 lines.push('')
2525
26 // ── Connection reuse (server context, applies to every site on this box) ──
27 //
28 // Every client here is remote: ~95ms RTT to the VPS, and a home backend adds
29 // ~110ms more over the VPN. A fresh TCP+TLS handshake therefore costs ~210ms —
30 // measured as HALF of a cold page load (fresh 427ms vs reused 210ms on
31 // /git/repos). Debian's default KeepAliveTimeout 5 closes the connection
32 // between one page view and the next click, so ordinary browsing re-pays that
33 // handshake constantly and reads to users as "it randomly needs to warm up".
34 //
35 // Emitted at TOP LEVEL (not inside a <VirtualHost>) so it covers the apex
36 // path-routed site — which serves /git — and not just the host-routed vhosts
37 // below. apache2.conf sets this at line ~124 and includes sites-enabled/ at
38 // ~238, so this later definition wins.
39 lines.push('# Override apache2.conf\'s default of 5 (see git history for the latency measurements).')
40 lines.push('KeepAliveTimeout 65')
41 lines.push('')
42
2643 const nodeMap = new Map()
2744 for (const node of registry.nodes) nodeMap.set(node.petname, node)
2845
package.json +1 -1
……@@ -1,6 +1,6 @@
11{
22 "name": "7rnx-proxy",
3 "version": "1.1.0",
3 "version": "1.2.0",
44 "description": "Reverse proxy management CLI for 7rnx.net Apache2 server",
55 "type": "module",
66 "bin": {
test/vhosts.test.js +23 -0
……@@ -40,6 +40,29 @@ describe('generateVhosts (host mode)', () => {
4040 hosts: [{ name: 'bad', node: 'x', port: 80, serverName: 'evil.com\n<script>' }]
4141 }), /unsafe serverName/)
4242 })
43
44 // Every client of this proxy is remote (~95ms RTT to the VPS, ~110ms more over
45 // the VPN to a home backend), so a TCP+TLS handshake costs ~210ms — measured half
46 // of a cold page load. Debian ships KeepAliveTimeout 5, which drops the connection
47 // between one page view and the next click, making the user re-pay that handshake
48 // and read as "it randomly needs to warm up again".
49 it('emits a KeepAliveTimeout override at SERVER context', () => {
50 const out = generateVhosts(registry)
51 assert.match(out, /^KeepAliveTimeout 65$/m, 'KeepAliveTimeout emitted')
52
53 // Must sit ABOVE the first <VirtualHost>: nested inside one it would apply to
54 // that vhost alone, leaving the apex site (which serves /git) on the default 5.
55 const ka = out.search(/^KeepAliveTimeout 65$/m)
56 const firstVhost = out.indexOf('<VirtualHost')
57 assert.ok(ka !== -1 && firstVhost !== -1, 'both directives present')
58 assert.ok(ka < firstVhost, 'KeepAliveTimeout precedes the first VirtualHost block')
59 })
60
61 // It is global tuning, not per-host, so it must not depend on registry contents.
62 it('emits the KeepAliveTimeout override even with no hosts registered', () => {
63 const out = generateVhosts({ nodes: [], hosts: [] })
64 assert.match(out, /^KeepAliveTimeout 65$/m)
65 })
4366})
4467
4568describe('Registry host methods', () => {