<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"
     xmlns:content="http://purl.org/rss/1.0/modules/content/"
     xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
  <title>justmeandlinux</title>
  <link>https://justmeandlinux.com/</link>
  <atom:link href="https://justmeandlinux.com/rss.xml" rel="self" type="application/rss+xml" />
  <description>Field notes on Linux, infrastructure and the things that break at 2am.</description>
  <language>en</language>
  <lastBuildDate>Fri, 02 Oct 2026 22:38:26 GMT</lastBuildDate>
  <item>
    <title>Data Center Part 8: The cheat sheet</title>
    <link>https://justmeandlinux.com/dc-08-cheat-sheet/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/dc-08-cheat-sheet/</guid>
    <pubDate>Fri, 21 Aug 2026 15:00:00 GMT</pubDate>
    <category>Linux</category>
    <description>Everything in this series on one page. Bookmark this one and skip the rest. Vocabulary, in one line each Term What it is U / RU Rack unit. 1.75 in / 44.45 mm of vertical mounting space. Cabinet Enclosed rack with doors and sides. What colo actually sells you…</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<p>Everything in this series on one page. Bookmark this one and skip the rest.</p>
<h2 id="vocabulary-in-one-line-each">Vocabulary, in one line each</h2>
<table>
<thead>
<tr>
<th>Term</th>
<th>What it is</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>U / RU</strong></td>
<td>Rack unit. 1.75 in / 44.45 mm of vertical mounting space.</td>
</tr>
<tr>
<td><strong>Cabinet</strong></td>
<td>Enclosed rack with doors and sides. What colo actually sells you.</td>
</tr>
<tr>
<td><strong>Cold aisle / hot aisle</strong></td>
<td>Fronts face fronts, backs face backs. Air goes front to back.</td>
</tr>
<tr>
<td><strong>Blanking panel</strong></td>
<td>Fills an empty U so hot air cannot recirculate to the intakes.</td>
</tr>
<tr>
<td><strong>PDU</strong></td>
<td>Power Distribution Unit. The outlet strip in the cabinet.</td>
</tr>
<tr>
<td><strong>Whip</strong></td>
<td>The flexible armoured cable delivering a feed to your cabinet.</td>
</tr>
<tr>
<td><strong>A-side / B-side</strong></td>
<td>The two independent power feeds. Usually blue and red.</td>
</tr>
<tr>
<td><strong>A-side / Z-side</strong></td>
<td>The two ends of a <em>circuit</em>. Unrelated to power A/B.</td>
</tr>
<tr>
<td><strong>DAC</strong></td>
<td>Direct Attach Copper. Transceivers moulded on. Short and cheap.</td>
</tr>
<tr>
<td><strong>AOC</strong></td>
<td>Active Optical Cable. Same, but fibre in the middle. ~100 m.</td>
</tr>
<tr>
<td><strong>Transceiver</strong></td>
<td>The removable optic. Takes a separate fibre lead.</td>
</tr>
<tr>
<td><strong>BMC</strong></td>
<td>The second computer in your server. iDRAC/iLO/XCC/CIMC.</td>
</tr>
<tr>
<td><strong>IPMI / Redfish</strong></td>
<td>Old protocol / modern REST protocol for talking to the BMC.</td>
</tr>
<tr>
<td><strong>NC-SI</strong></td>
<td>Sideband letting the BMC share a data NIC instead of its own port.</td>
</tr>
<tr>
<td><strong>ToR</strong></td>
<td>Top of Rack. The switch in your cabinet.</td>
</tr>
<tr>
<td><strong>Leaf-spine</strong></td>
<td>Every leaf to every spine. Three hops between any two servers.</td>
</tr>
<tr>
<td><strong>Oversubscription</strong></td>
<td>Downstream capacity ÷ upstream capacity. 3:1 is normal.</td>
</tr>
<tr>
<td><strong>Demarc</strong></td>
<td>The physical boundary of responsibility. Usually a patch panel.</td>
</tr>
<tr>
<td><strong>MMR</strong></td>
<td>Meet-Me Room. Where every carrier and cloud terminates.</td>
</tr>
<tr>
<td><strong>Cross-connect</strong></td>
<td>A rented physical cable between two points in the building.</td>
</tr>
<tr>
<td><strong>LOA-CFA</strong></td>
<td>The document authorising a cross-connect. Names cabinet/panel/port.</td>
</tr>
<tr>
<td><strong>Direct Connect</strong></td>
<td>A cross-connect to a cloud. Dedicated port or hosted slice.</td>
</tr>
<tr>
<td><strong>Transit / peering / IX</strong></td>
<td>Reach everything (paid) / reach one network (free) / reach many from one port.</td>
</tr>
<tr>
<td><strong>Remote hands</strong></td>
<td>Follows precise instructions. Usually included.</td>
</tr>
<tr>
<td><strong>Smart hands</strong></td>
<td>Racking, cabling, part swaps. Chargeable. Needs judgement.</td>
</tr>
</tbody>
</table>
<h2 id="rack-numbers">Rack numbers</h2>
<ul>
<li><strong>19 in / 482.6 mm</strong> — mounting face width (EIA-310). The <em>cabinet</em> is wider.</li>
<li><strong>1U = 1.75 in = 44.45 mm.</strong> Holes in uneven groups of three per U.</li>
<li><strong>Cabinet width</strong>: 600 mm, or 800 mm when you want side cable channels.</li>
<li><strong>Cabinet depth</strong>: 600 / 800 / 1000 / 1200 mm. Datacenter servers need 800+.</li>
<li><strong>600 mm deep = network cabinet.</strong> Will not take a server.</li>
<li><strong>0U</strong> = mounts vertically in the side channel. PDUs. Costs you no U.</li>
<li><strong>Heavy at the bottom.</strong> Always.</li>
<li><strong>U1 is usually the bottom, but not universally.</strong> Check your rails and write it down.</li>
<li>Multi-U devices are referenced by their <strong>lowest</strong> U.</li>
</ul>
<h2 id="power">Power</h2>
<p><strong>Usable capacity at 80 % of breaker rating</strong> (US NEC continuous-load rule; elsewhere the same practical outcome via PDU rating and contract):</p>
<table>
<thead>
<tr>
<th>Feed</th>
<th>Nameplate</th>
<th>Usable</th>
</tr>
</thead>
<tbody>
<tr>
<td>120 V / 20 A / 1Ø</td>
<td>2.4 kW</td>
<td>1.92 kW</td>
</tr>
<tr>
<td>208 V / 20 A / 1Ø</td>
<td>4.16 kW</td>
<td>3.33 kW</td>
</tr>
<tr>
<td>208 V / 30 A / 1Ø</td>
<td>6.24 kW</td>
<td>4.99 kW</td>
</tr>
<tr>
<td>208 V / 30 A / 3Ø</td>
<td>10.8 kW</td>
<td>8.65 kW</td>
</tr>
</tbody>
</table>
<p>Three-phase: <code>kW = 1.732 × V × A</code>.</p>
<p><strong>The redundancy rule: in a 2N cabinet, 40 % per feed is full.</strong> Either feed must be able to carry the whole cabinet at 80 %. Running both feeds at 70 % means a single feed failure trips the survivor — that is <strong>swing load</strong>, and it turns one failure into a total outage.</p>
<p><strong>IEC connectors</strong> — odd is socket, next even up is the matching inlet:</p>
<table>
<thead>
<tr>
<th></th>
<th>Socket (on PDU)</th>
<th>Inlet (on kit)</th>
<th>Rating</th>
</tr>
</thead>
<tbody>
<tr>
<td>Normal</td>
<td><strong>C13</strong></td>
<td><strong>C14</strong></td>
<td>10 A</td>
</tr>
<tr>
<td>Hungry</td>
<td><strong>C19</strong></td>
<td><strong>C20</strong></td>
<td>16 A</td>
</tr>
</tbody>
</table>
<p>C15/C16 is the heat-rated variant with a notch — not interchangeable with C13.</p>
<p><strong>NEMA input plugs</strong> (PDU → whip): <strong>L6-30P</strong> (208 V 30 A 1Ø, most common), <strong>L6-20P</strong>, <strong>L21-30P</strong> (3Ø wye), <strong>L15-30P</strong> (3Ø delta). <code>L</code> = twist-locking.</p>
<p><strong>PDU tiers</strong>: basic → metered (total draw) → monitored (per outlet) → <strong>switched</strong> (per-outlet relays). Get switched and monitored; it is the only remote power cycle that works when the BMC is wedged.</p>
<h2 id="cables-and-optics">Cables and optics</h2>
<p><strong>Reach:</strong></p>
<table>
<thead>
<tr>
<th></th>
<th>Typical max</th>
</tr>
</thead>
<tbody>
<tr>
<td>Passive DAC</td>
<td>~3 m (5–7 m at some speeds)</td>
</tr>
<tr>
<td>Active DAC</td>
<td>~5–10 m</td>
</tr>
<tr>
<td>AOC</td>
<td>~100 m</td>
</tr>
<tr>
<td>Transceiver + fibre</td>
<td>metres to tens of km</td>
</tr>
</tbody>
</table>
<p><strong>Form factors</strong> — <code>Q</code> means four lanes:</p>
<table>
<thead>
<tr>
<th></th>
<th>Speed</th>
</tr>
</thead>
<tbody>
<tr>
<td>SFP</td>
<td>1 Gb</td>
</tr>
<tr>
<td>SFP+</td>
<td>10 Gb</td>
</tr>
<tr>
<td>SFP28</td>
<td>25 Gb</td>
</tr>
<tr>
<td>QSFP+</td>
<td>40 Gb (4 × 10)</td>
</tr>
<tr>
<td>QSFP28</td>
<td>100 Gb (4 × 25)</td>
</tr>
<tr>
<td>QSFP-DD / OSFP</td>
<td>200–800 Gb</td>
</tr>
</tbody>
</table>
<p><strong>Breakout</strong>: 1×100G → 4×25G, 1×40G → 4×10G. Because Q is four lanes.</p>
<p><strong>Fibre</strong> — the jacket colour is reliable:</p>
<table>
<thead>
<tr>
<th>Type</th>
<th>Core</th>
<th>Colour</th>
<th>Grades</th>
</tr>
</thead>
<tbody>
<tr>
<td>Multimode</td>
<td>50 µm</td>
<td><strong>aqua</strong> (OM5: lime)</td>
<td>OM3, OM4, OM5</td>
</tr>
<tr>
<td>Singlemode</td>
<td>9 µm</td>
<td><strong>yellow</strong></td>
<td>OS2</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th>Link</th>
<th>OM3</th>
<th>OM4</th>
<th>OS2</th>
</tr>
</thead>
<tbody>
<tr>
<td>10 Gb</td>
<td>~300 m</td>
<td>~400 m</td>
<td>10 km</td>
</tr>
<tr>
<td>40 Gb (SR4)</td>
<td>~100 m</td>
<td>~150 m</td>
<td>10 km</td>
</tr>
<tr>
<td>100 Gb (SR4)</td>
<td>~70 m</td>
<td>~100 m</td>
<td>10 km</td>
</tr>
</tbody>
</table>
<p><strong>Transceiver naming</strong>: <strong>SR</strong> = short reach, multimode. <strong>LR</strong> = long reach, singlemode, 10 km. <strong>ER/ZR</strong> = further. A trailing <strong>4</strong> means four lanes, so eight fibres, so <strong>MPO</strong> rather than <strong>LC</strong>.</p>
<p><strong>Connectors</strong>: <strong>LC</strong> duplex (2 fibres, the common one), <strong>MPO/MTP</strong> (12 or 24 fibres, for parallel optics and trunks), <strong>SC</strong> (older, carrier hand-offs).</p>
<p><strong>Copper</strong>: Cat5e = 1 Gb, Cat6 = 10 Gb to 55 m, <strong>Cat6A = 10 Gb to 100 m</strong>. 10GBASE-T is hotter, slower-latency and more power-hungry than DAC — which is why it survives mainly for BMC and cheap kit.</p>
<p><strong>Choosing</strong>: same cabinet → DAC. Adjacent → AOC. Through a patch panel → transceivers and fibre, always (you cannot patch an AOC).</p>
<p>Transceivers are <strong>vendor-coded</strong>; specify the switch model when ordering. <strong>DOM/DDM</strong> exposes Rx power per module — scrape it, it is the best early warning of a dying link.</p>
<h2 id="out-of-band">Out-of-band</h2>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">BMC</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"-I lanplus -H 10.20.0.37 -U admin -P </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">$PW</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ipmitool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $BMC </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">chassis</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> power</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> status</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">on</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">off</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">soft</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">cycle</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">reset</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ipmitool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $BMC </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">chassis</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> identify</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 300</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">     # light the blue ID LED, 300s</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ipmitool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $BMC </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">sel</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> elist</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                # hardware event log — run this first</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ipmitool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $BMC </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">sdr</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> list</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                 # all sensors</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ipmitool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $BMC </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">lan</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> print</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">              # BMC IP/MAC/VLAN, cipher suites</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ipmitool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $BMC </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">sol</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> activate</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">             # serial over LAN  (escape: ~. )</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ipmitool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $BMC </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">chassis</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> bootdev</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> pxe</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">      # override next boot</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ipmitool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $BMC </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">mc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> reset</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> cold</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">            # reboot the BMC, NOT the server</span></span>
<span class="line"></span></code></pre>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Redfish equivalent</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -sk</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -u</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "admin:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">$PW</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> https://</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">$BMC</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/redfish/v1/Systems/1</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> jq</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> .PowerState</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -sk</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -u</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "admin:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">$PW</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -X</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> POST</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -H</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'Content-Type: application/json'</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  -d</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '{"ResetType":"ForceRestart"}'</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  https://</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">$BMC</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/redfish/v1/Systems/1/Actions/ComputerSystem.Reset</span></span>
<span class="line"></span></code></pre>
<p><strong>Serial console, set up in advance:</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># /etc/default/grub</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">GRUB_CMDLINE_LINUX</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"console=tty0 console=ttyS0,115200n8"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">GRUB_TERMINAL</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"console serial"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">GRUB_SERIAL_COMMAND</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"serial --speed=115200 --unit=0"</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># then</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">update-grub</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> &#x26;&#x26; </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">systemctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> enable</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --now</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> serial-getty@ttyS0.service</span></span>
<span class="line"></span></code></pre>
<p><strong>The reboot ladder:</strong></p>
<ol>
<li><code>ssh host reboot</code> — needs a working OS.</li>
<li><code>ipmitool ... chassis power cycle</code> — needs a reachable BMC. <strong>Does not remove standby power.</strong></li>
<li><strong>Switched PDU, both outlets off, 30 seconds</strong> — removes standby power. Clears a wedged BMC.</li>
<li>Remote hands ticket.</li>
</ol>
<p><strong>Security — non-negotiable:</strong> dedicated management VLAN, no internet route, reach via jump host or VPN. <strong>Disable cipher suite 0</strong> (no authentication at all). IPMI 2.0's RAKP exchange leaks password hashes pre-authentication by design, so an exposed BMC is an offline cracking target. Change default credentials. Prefer Redfish; disable IPMI-over-LAN if unused. Patch BMC firmware.</p>
<p><strong>Dedicated management port beats shared/NC-SI.</strong> Shared puts your recovery path in the same failure domain as the thing that breaks.</p>
<h2 id="network-placement">Network placement</h2>
<ul>
<li><strong>ToR</strong> — switch per cabinet, short DAC to servers, fibre uplinks out. The default.</li>
<li><strong>EoR / MoR</strong> — chassis at the row end or middle, long structured cabling. Better when cabinets are sparse or you want intra-row traffic on one switch.</li>
<li><strong>Leaf-spine</strong> — every leaf to every spine, no leaf-leaf, no spine-spine. Every server three hops from every other. Scales by adding spines.</li>
<li><strong>Oversubscription</strong> = downstream ÷ upstream. <code>48×25G ÷ 8×100G = 1.5:1</code>. 3:1 is common. <strong>Know yours and graph the uplinks</strong> — that is where unexplained slowness lives.</li>
<li><strong>Two ToR switches per cabinet</strong>, plus a cheap 1 Gb management switch for BMCs.</li>
<li><strong>Check switch airflow direction</strong> when ordering — port-side intake vs exhaust. The wrong one inhales its neighbour's exhaust.</li>
</ul>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ethtool</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eno1</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -E</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'Speed|Duplex|Link detected'</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">cat</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /proc/net/bonding/bond0</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">lldpctl</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                 # which switch, which port — learn this one</span></span>
<span class="line"></span></code></pre>
<h2 id="connectivity">Connectivity</h2>
<p><strong>The chain</strong>: your router → <strong>demarc panel</strong> → <strong>cross-connect</strong> → <strong>MMR</strong> → carrier / cloud / IX.</p>
<ul>
<li>A cross-connect is <strong>a cable, not a service</strong>. Install fee plus monthly rent. Takes days.</li>
<li>Order it with an <strong>LOA-CFA</strong> from the far end, naming cabinet, panel and port. <strong>They expire.</strong></li>
<li><strong>Direct Connect</strong>: <em>dedicated</em> = a whole port (AWS: 1/10/100/400 Gbps), you order the cross-connect. <em>Hosted</em> = a partner's slice (AWS: 50 Mbps–25 Gbps), no cross-connect for you.</li>
<li><strong>Transit</strong> = reach everything, paid. <strong>Peering</strong> = direct to one network, usually free, only their customers. <strong>IX</strong> = one cross-connect, hundreds of peering sessions.</li>
<li><strong>95th percentile billing</strong>: five-minute samples, top 5 % discarded, pay on the highest remainder. Roughly the top 36 hours a month are free — bursts are free, sustained growth is not. A <strong>commit</strong> is a minimum you pay regardless; ~10 % of port capacity is a common shape.</li>
<li><strong>"Redundant" means physically diverse.</strong> Ask what the two paths share: panel, room, building, conduit.</li>
</ul>
<h2 id="the-remote-hands-ticket">The remote hands ticket</h2>
<pre data-lang-none><code>SUBJECT: [action] on [device] at [cabinet] / [U]

LOCATION   facility · cage · cabinet · U position · FRONT or REAR
           · ID LED lit?
DEVICE     hostname · make/model · service tag · where the label is
STEPS      numbered · every port and outlet named · nothing assumed
DO NOT     what must not be touched
VERIFY     how you will confirm
IF DIFFERENT   stop and call me — explicit permission to stop
PHOTOS     rear cabling · both PDU outlets · front of rack
URGENCY    production impact · target time
</code></pre>
<p>Rules that do the work:</p>
<ul>
<li><strong>Say FRONT or REAR every time.</strong> Left and right are <em>as viewed from the rear</em>.</li>
<li><strong>Light the ID LED</strong> before filing. It removes identity ambiguity for free.</li>
<li><strong>Both cords, different PDUs</strong> — state it twice on any install.</li>
<li><strong>Distinguish the BMC port</strong> from the data ports explicitly.</li>
<li><strong>Label both ends with the far end's identity.</strong></li>
<li><strong>Ask for photos.</strong> Always. It is your only durable record.</li>
<li><strong>Never ask them to diagnose</strong>, and never ask them to log in.</li>
<li><strong>Verify the result, not the report</strong> — <code>lldpctl</code>, link counters, DOM levels, <code>sel elist</code>, cabinet power draw.</li>
</ul>
<p>Notify the facility of <strong>inbound shipments</strong> in advance with tracking numbers and cage marking, and say what should happen to the packaging.</p>
<h2 id="the-five-facts-about-your-own-cage">The five facts about your own cage</h2>
<p>If you can answer these without looking, you have closed the gap this series was written for.</p>
<ol>
<li>Cabinet identifier — room, row, position — and whether U1 is top or bottom.</li>
<li>Free U, and usable post-to-post depth.</li>
<li>Power: how many feeds, what rating, what you are actually drawing.</li>
<li>Uplinks: what the ToR connects to, spare ports, oversubscription ratio.</li>
<li>Cross-connects: how many leave the cage, where each terminates, and what your "redundant" pair physically shares.</li>
</ol>
<h2 id="where-to-verify-this">Where to verify this</h2>
<p>Standards and vendor documentation beat blog posts, including this one. The primary sources behind this series:</p>
<ul>
<li><strong>EIA-310 / rack dimensions</strong> — <a href="https://en.wikipedia.org/wiki/Rack_unit">rack unit reference</a>, <a href="https://www.kdstelectrical.com/19-inch-rack-standards/">19-inch rack standards</a></li>
<li><strong>Rack power and A/B feeds</strong> — <a href="https://servercentral.com/blog/correctly-size-power-requirements-server-racks">ServerCentral on sizing rack power</a>, <a href="https://www.belden.com/blog/data-centers/exploring-dual-power-feeds-in-data-centers">Belden on dual power feeds</a></li>
<li><strong>DAC / AOC reach</strong> — <a href="https://www.fs.com/c/100g-qsfp28-dac-2873">FS 100G DAC/AOC</a>, <a href="https://www.ampcom.com/blogs/industry-insights/what-are-dac-cables-types-latency-use-cases">DAC selection guide</a></li>
<li><strong>Fibre grades and reach</strong> — <a href="https://edgeoptic.com/kb_article/multimode-fiber-types">multimode fibre types</a>, <a href="https://www.l-p.com/blog/knowledge-center/multimode-distance-limits-om3-vs-om4-fiber-standards.htm">OM3 vs OM4 distance limits</a></li>
<li><strong>BMC / IPMI</strong> — <a href="https://cycle.io/learn/out-of-band-management">out-of-band management overview</a>, <a href="https://www.avalue.com/en/support/faq/General/Dedicated-vs-Shared-IPMI-port_164">dedicated vs shared IPMI port</a></li>
<li><strong>BMC security</strong> — <a href="https://www.rapid7.com/blog/post/2013/07/02/a-penetration-testers-guide-to-ipmi/">Rapid7's penetration tester's guide to IPMI</a>, <a href="https://www.runzero.com/blog/bmc-research/">runZero BMC research</a>, <a href="https://thehackernews.com/2026/07/24650-internet-exposed-bmcs-disclose.html">internet-exposed BMCs leaking hashes</a></li>
<li><strong>Switch placement and fabric</strong> — <a href="https://dc.mynetworkinsights.com/data-center-switching-centralized-eor-mor-top-of-rack-tor/">ToR vs EoR vs MoR</a>, <a href="https://www.arista.com/assets/data/pdf/Whitepapers/7300_EoR_Catalyst_6500_Upgrade_Guide.pdf">Arista EoR/MoR white paper</a></li>
<li><strong>MMR, demarc, cross-connect</strong> — <a href="https://www.volico.com/what-is-a-meet-me-room-and-why-is-it-crucial-in-a-data-center/">meet-me room explained</a>, <a href="https://macronetservices.com/data-center-cross-connect-guide/">cross-connect guide</a></li>
<li><strong>LOA-CFA and Direct Connect</strong> — <a href="https://docs.aws.amazon.com/directconnect/latest/UserGuide/Colocation.html">AWS: requesting cross connects at Direct Connect locations</a>, <a href="https://docs.aws.amazon.com/directconnect/latest/UserGuide/download-loa-cfa.html">AWS: download the LOA-CFA</a>, <a href="https://aws.amazon.com/blogs/networking-and-content-delivery/aws-direct-connect-layer-1-explained-from-data-centers-to-cloud-connectivity/">AWS Direct Connect layer 1 explained</a></li>
<li><strong>Transit, peering, IX, 95th percentile</strong> — <a href="https://blog.equinix.com/blog/2018/12/10/networking-for-nerds-do-you-know-the-difference-between-ip-peering-vs-ip-transit-for-enterprise-internet-interconnection/">Equinix on peering vs transit</a>, <a href="https://www.kentik.com/kentipedia/ip-transit-vs-peering/">Kentik on transit vs peering</a>, <a href="https://www.noction.com/blog/95th-percentile-explained">95th percentile explained</a></li>
<li><strong>Labelling and structured cabling</strong> — <a href="https://silverfoxlabeling.com/blogs/article/tia-606-cable-labeling-compliance-guide">TIA-606 labelling guide</a></li>
<li><strong>Remote hands vs smart hands</strong> — <a href="https://www.datacenters.com/news/remote-hands-vs-smart-hands-services-what-s-the-difference">datacenters.com comparison</a>, <a href="https://www.techtarget.com/searchdatacenter/definition/remote-hands">TechTarget definition</a></li>
</ul>
<p>Numbers vary by vendor and by facility contract. Where a figure here matters to a purchase or a design, confirm it against the specific part and the specific site.</p>
<p>That is the series. If it worked, the next facility email you get should read as sentences rather than acronyms — and the next ticket you write should come back done, first time.</p>]]></content:encoded>
  </item>
  <item>
    <title>Data Center Part 7: Directing remote hands</title>
    <link>https://justmeandlinux.com/dc-07-remote-hands/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/dc-07-remote-hands/</guid>
    <pubDate>Fri, 21 Aug 2026 14:00:00 GMT</pubDate>
    <category>Linux</category>
    <description>The technician reading your ticket is standing in a cold, loud room, in front of forty identical black boxes, wearing gloves, holding a tablet. They cannot see your monitoring. They do not know your naming scheme. They have another six tickets after yours…</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<p>The technician reading your ticket is standing in a cold, loud room, in front of forty identical black boxes, wearing gloves, holding a tablet. They cannot see your monitoring. They do not know your naming scheme. They have another six tickets after yours.</p>
<p>Every ambiguity you leave becomes a phone call, a delay, or the wrong cable being unplugged. This part is how to not do that.</p>
<h2 id="the-one-principle">The one principle</h2>
<p><strong>Write instructions that can be followed by someone competent who knows nothing about your systems.</strong></p>
<p>That is the whole discipline. Not dumbed down — the technician is skilled, often far more skilled with hardware than you are. But <em>context-free</em>. They know racks, cables, screws and connectors. They do not know that <code>srv-web-11</code> is the one that matters, or that "the storage box" means the 4U at the bottom.</p>
<p>The two failure modes that follow from ignoring this are worth naming, because they are the ones that actually happen. <strong>Ambiguity</strong>, where the instruction is unclear and the technician either guesses or calls you. And <strong>under-specification</strong>, where you assume a step is obvious and it is not, so the job comes back half-done.</p>
<h2 id="the-ticket-template">The ticket template</h2>
<p>Every remote hands request should have these sections. Not all of them will have content every time, but the headings stop you forgetting one.</p>
<pre data-lang-none><code>SUBJECT: [action] on [device] at [cabinet] / [U position]

LOCATION
  Facility:      DC1, 123 Example Road
  Cage / suite:  Cage 14
  Cabinet:       ROW-C RACK-07   (label on door: "C07")
  U position:    U37   (numbered from the bottom; U1 is the lowest)
  Front or rear: REAR
  Identifying:   ID LED is lit and flashing blue, front and rear

DEVICE
  Hostname:      srv-web-11
  Make/model:    Dell PowerEdge R650, 1U
  Service tag:   ABC1234
  Asset label:   on the pull-out tab, front right

WHAT I NEED DONE
  1. ...
  2. ...
  3. ...

WHAT I DO NOT WANT TOUCHED
  ...

HOW I WILL VERIFY
  ...

IF SOMETHING IS DIFFERENT FROM THIS DESCRIPTION
  Stop and contact me. Do not improvise. I am on &lt;phone&gt; until &lt;time&gt;.

ESCALATION / URGENCY
  Production impact: yes/no. Target: within N hours.
</code></pre>
<p>Four of those sections do disproportionate work.</p>
<p><strong>Front or rear.</strong> Say it explicitly, every time. Almost all work is at the rear; almost all descriptions unconsciously assume the front. And when you say left or right, say <em>as viewed from the rear</em> — a rear-facing observer's left is the front-facing observer's right, and this exact confusion unplugs the wrong power cord.</p>
<p><strong>The ID LED.</strong> From Part 4: light it before you file the ticket. <code>ipmitool ... chassis identify 300</code> turns "the third one down" into "the one flashing blue". If the BMC is unreachable, fall back on the service tag or asset label, and say where the label physically is.</p>
<p><strong>What I do not want touched.</strong> The most valuable line in the ticket, and the one people leave out. "Do not disturb any other cable in this cabinet. If a cable must be moved to gain access, photograph it first and put it back exactly as found."</p>
<p><strong>Stop and contact me.</strong> Explicitly give permission to stop. Without it, a conscientious technician facing a situation that does not match your description will try to make it work, and that is where damage happens.</p>
<h2 id="standard-job-rack-and-cable-a-new-server">Standard job: rack and cable a new server</h2>
<p>This is the big one and it is a <strong>smart hands</strong> job, not remote hands — it needs judgement and hardware skill. Everything in Parts 1 through 5 shows up here.</p>
<pre data-lang-none><code>SUBJECT: Install and cable 1x Dell R650 in ROW-C RACK-07 at U20-U20

LOCATION
  Cage 14, ROW-C RACK-07, U20 (1U). Numbered from the bottom.

HARDWARE
  Shipment: tracking 1Z999..., delivered &lt;date&gt;, marked "ATTN: Cage 14"
  Contents: 1x Dell PowerEdge R650 (1U) + ReadyRails sliding rail kit
            2x C13-C14 power cords (1x blue, 1x red)
            2x 2m SFP28 passive DAC
            1x 2m Cat6A patch lead (grey)
  Service tag on the box: ABC1234

MECHANICAL
  1. Install the sliding rails at U20, front and rear posts.
     Square-hole rack; cage nuts as required by the kit.
  2. Rack the server at U20. Confirm the front bezel sits flush and the
     rails travel freely through their full extension.
  3. Fit the cable management arm supplied with the kit.
  4. Leave a service loop on all rear cables so the server can be pulled
     fully forward without unplugging anything.

POWER   (two cords — they must go to DIFFERENT PDUs)
  5. Blue cord: PSU1 -&gt; PDU-A, outlet A14.
  6. Red cord:  PSU2 -&gt; PDU-B, outlet B14.
     ** Both cords must NOT be in the same PDU. **
  7. Engage the retention clips on both outlets.

DATA
  8. DAC 1: server NIC port 1 (left port as viewed from the REAR)
            -&gt; switch sw-c07-01, port Et14
  9. DAC 2: server NIC port 2 (right port as viewed from the REAR)
            -&gt; switch sw-c07-02, port Et14

MANAGEMENT
 10. Cat6A grey: the RJ45 labelled "iDRAC" on the server rear
            -&gt; switch mgmt-sw-01, port 14
     This is the small dedicated port, NOT one of the four LOM ports.

LABELLING
 11. Label both ends of every cable with the far end, e.g.
     server end of DAC1:  "-&gt; sw-c07-01 Et14"
     switch end of DAC1:  "-&gt; srv-web-14 p1"

POWER ON
 12. Power the server on at the front button. Confirm both PSU LEDs are
     green and the front status LED is not amber.

PHOTOS PLEASE
 13. Rear of the server showing all five cables seated.
 14. PDU-A outlet A14 and PDU-B outlet B14, labels visible.
 15. Front of the rack showing the server at U20 with the bezel on.

DO NOT
  Do not disturb any other equipment or cabling in this cabinet.
  If anything does not match the above, stop and call me.
</code></pre>
<p>The things that make that ticket work: <strong>every port has a name</strong>, <strong>every cable has a colour</strong>, <strong>the A/B constraint is stated twice</strong>, <strong>the management port is distinguished from the data ports explicitly</strong> (Part 4's dedicated-versus-shared distinction, expressed as a physical instruction), and <strong>the photos let you verify all of it without trusting anybody's memory</strong>.</p>
<p>Ask for photos on every install. It costs the technician thirty seconds and it is the only durable record of how the cabinet was left.</p>
<h2 id="standard-job-patch-a-port">Standard job: patch a port</h2>
<p>Short, but the same rules apply.</p>
<pre data-lang-none><code>SUBJECT: Patch panel PP-01 port 14 -&gt; switch sw-c07-01 port Et22

Cage 14, ROW-C RACK-07. REAR of cabinet.

  1. Take a 1m LC-LC duplex singlemode (yellow) patch lead from the
     spares bin in this cabinet.
  2. Connect patch panel PP-01, port 14  -&gt;  switch sw-c07-01, port Et22.
  3. Clean both ends before insertion.
  4. Label both ends.
  5. Photograph the panel and the switch port.

Do not remove or move any existing lead. If port 14 is already occupied,
STOP and tell me what is in it — do not unplug it.
</code></pre>
<p>That last paragraph exists because "patch port 14" and "port 14 already has something in it" is a very common collision, and without an instruction the technician has to guess whether the existing lead is stale or live.</p>
<p>The fibre-cleaning line is not fussiness. From Part 3: a fingerprint on a ferrule is enough to kill a 100 Gb link.</p>
<h2 id="standard-job-reseat">Standard job: reseat</h2>
<p>The most common ticket there is, and the least likely to go wrong — provided you say which end.</p>
<pre data-lang-none><code>SUBJECT: Reseat DAC on srv-web-11 NIC port 1

Cage 14, ROW-C RACK-07, U37. REAR. ID LED is lit (flashing blue).

  1. Locate the DAC in server NIC port 1 (left as viewed from the REAR).
     It should be labelled "-&gt; sw-c07-01 Et14".
  2. Unplug it at the SERVER end only. Wait 10 seconds. Plug it back in
     firmly until it clicks.
  3. Tell me the link LED state on the server port before and after.
  4. If there is still no link, repeat at the SWITCH end (sw-c07-01,
     port Et14) and tell me again.

I am watching the switch port; I will confirm within 30 seconds of each step.
</code></pre>
<p>Note the sequencing — one end at a time, with a report between. If you ask for both ends at once and it comes back working, you have learned nothing about which end was faulty.</p>
<h2 id="standard-job-power-cycle-at-the-pdu">Standard job: power cycle at the PDU</h2>
<p>This is rung 3 of the ladder from Part 4, and it has a specific trap.</p>
<pre data-lang-none><code>SUBJECT: Full power cycle of srv-db-03 at the PDU (BMC unresponsive)

Cage 14, ROW-C RACK-07, U12. REAR.
The BMC is not answering, so I cannot do this myself and the ID LED
is NOT lit. Identify by the service tag XYZ5678 on the front pull-tab.

This server is DUAL-CORDED. BOTH cords must be removed for this to work.

  1. Confirm the two cords for this server: PDU-A outlet A06 (blue)
     and PDU-B outlet B06 (red). Both are labelled "srv-db-03".
  2. Unplug BOTH cords.
  3. Wait 30 seconds. (Less than this and standby power does not drain,
     which is the whole point of the exercise.)
  4. Plug both back in, blue to A06 and red to B06.
  5. Confirm both PSU LEDs come up green.

Do not use the front power button. I need standby power removed.
</code></pre>
<p>Two things there are the whole reason this ticket exists in this form. <strong>Both cords</strong>, because removing one from a redundant machine changes nothing. And <strong>thirty seconds</strong>, because the objective is draining standby power to clear the BMC, and a quick out-and-in does not do it.</p>
<h2 id="standard-job-read-me-the-state">Standard job: read me the state</h2>
<p>The cheapest and most underused request. When you have no out-of-band access at all, a technician's eyes are a diagnostic instrument.</p>
<pre data-lang-none><code>SUBJECT: Report physical status of srv-db-03

Cage 14, ROW-C RACK-07, U12. Service tag XYZ5678.

Please report, front and rear:
  1. Front status LED: off / green / flashing amber / solid amber?
  2. Any text on the front LCD panel? Photograph it if present.
  3. Both PSU LEDs at the rear: green / amber / off? Which is which?
  4. Both NIC link LEDs: lit? flashing? dark?
  5. Are the fans audibly spinning?
  6. Photograph the front and the rear.

No action needed — I only need the observations.
</code></pre>
<p>An amber PSU LED and a photographed LCD reading a fault code will frequently tell you exactly what is wrong before you have opened a hardware support case, and it costs one short ticket.</p>
<h2 id="what-not-to-ask-for">What not to ask for</h2>
<p>Three categories, and knowing them saves you a rejected ticket and some embarrassment.</p>
<p><strong>Do not ask them to diagnose.</strong> "Can you see why it's not working?" is not a task. Give them observations to make and steps to take. They do not know what "working" looks like for your system.</p>
<p><strong>Do not ask them to touch your data or your configuration.</strong> They do not log into your servers. If a job needs a login, that is your side of the demarc — figuratively as well as literally. Where a technician does need console access during, say, a firmware recovery, that is arranged explicitly with credentials scoped to the task, not assumed.</p>
<p><strong>Do not ask for anything ambiguous about identity.</strong> "The server that's broken", "the new one", "the third from the top". Use the U position, the hostname, the service tag, and the ID LED.</p>
<p>And one thing to be careful with rather than avoid: <strong>anything that risks other equipment</strong>. Moving a bundle to get access, removing a blanking panel, sliding a machine forward on rails in a densely cabled cabinet. Those are fine to ask for, but say explicitly what may be disturbed and ask for before-and-after photographs.</p>
<h2 id="shipping-hardware-in">Shipping hardware in</h2>
<p>A related process that goes wrong more often than the technical work.</p>
<p>Most facilities require <strong>advance notice of an inbound shipment</strong>, with a ticket giving the tracking number, the expected date, the number of packages and the destination cage. Without it, the loading dock may refuse delivery or the boxes may sit in a receiving area nobody told you about.</p>
<p>Mark the boxes with your <strong>company name and cage number</strong>, not just an address. Say in the ticket whether you want them <strong>unboxed and staged</strong>, or <strong>left sealed</strong>. And ask them to <strong>record the service tags of what arrived</strong> and photograph any physically damaged packaging before it is opened — that is the evidence you need if a machine turns out to be dead on arrival.</p>
<p>Storage of packaging and empty boxes is usually charged or prohibited. Say what should happen to the cardboard.</p>
<h2 id="verify-everything">Verify everything</h2>
<p>Every ticket ends with your verification, and the point is to check the <em>result</em>, not the report.</p>
<ul>
<li>After a <strong>rack and cable</strong>: BMC pings on the management VLAN, <code>lldpctl</code> shows the expected switch and port on both NICs, both PSUs report present and drawing power, and the photographs show A and B outlets.</li>
<li>After a <strong>patch</strong>: the link comes up and optical Rx power is in range. From Part 4 — if you are graphing DOM, you can see the level, not just the up/down.</li>
<li>After a <strong>reseat</strong>: the switch port counter stops incrementing errors, not merely that the link is up.</li>
<li>After a <strong>power cycle</strong>: the machine boots and <code>ipmitool ... sel elist</code> shows the power event where you expect it.</li>
</ul>
<p>And after any physical work in a cabinet, glance at the cabinet's total power draw on the PDUs. An unexpected change means something was plugged in or unplugged that you did not ask about.</p>
<h2 id="the-one-line-summary">The one-line summary</h2>
<p>Write for a skilled person who knows nothing about your systems. <strong>Cabinet, U position, front or rear, and the ID LED.</strong> Name every port and every outlet. State the A/B constraint explicitly. Say what must not be touched, give explicit permission to stop, ask for photographs, and verify the result yourself rather than trusting the report.</p>
<p>Next up: <strong>Part 8 — the cheat sheet</strong>, which is every number, connector and command in this series on one page.</p>]]></content:encoded>
  </item>
  <item>
    <title>Data Center Part 6: Leaving the cage</title>
    <link>https://justmeandlinux.com/dc-06-leaving-the-cage/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/dc-06-leaving-the-cage/</guid>
    <pubDate>Fri, 21 Aug 2026 13:00:00 GMT</pubDate>
    <category>Linux</category>
    <description>Everything up to this point stayed inside your cage. This part is about the handful of strands that leave it, and about the paperwork that governs them — because the paperwork is genuinely the hard part. The chain, end to end Almost all confusion here…</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<p>Everything up to this point stayed inside your cage. This part is about the handful of strands that leave it, and about the paperwork that governs them — because the paperwork is genuinely the hard part.</p>
<h2 id="the-chain-end-to-end">The chain, end to end</h2>
<p>Almost all confusion here dissolves once you can draw the path. It is four links.</p>
<pre data-lang-none><code>  ┌── YOUR CAGE ───────────────────────┐
  │                                     │
  │  server ── ToR switch ── your edge  │
  │                          router     │
  │                             │       │
  │                        ┌────┴────┐  │
  │                        │ DEMARC  │  │  ← the boundary of responsibility.
  │                        │ PANEL   │  │    Your side / their side.
  │                        └────┬────┘  │
  └─────────────────────────────┼───────┘
                                │
                    ╔═══════════╧═══════════╗
                    ║      CROSS-CONNECT     ║  ← a physical cable the
                    ║  (a cable, not a       ║    facility installs and
                    ║   service)             ║    charges rent on.
                    ╚═══════════╤═══════════╝
                                │
  ┌── MEET-ME ROOM (MMR) ───────┼──────────────────────────────┐
  │  restricted room. Every carrier, cloud on-ramp and         │
  │  tenant terminates here on fibre distribution frames.      │
  │                                                             │
  │   [carrier A]  [carrier B]  [AWS]  [Azure]  [the IX]  ...  │
  └─────────────────────────────────────────────────────────────┘
</code></pre>
<p>Your responsibility ends at the demarc. The cross-connect is the facility's to install and maintain. The far end belongs to whoever you connected to.</p>
<h2 id="demarc">Demarc</h2>
<p>The <strong>demarcation point</strong> is a boundary of responsibility, expressed as a physical object — normally a patch panel in or beside your cage.</p>
<p>The reason it matters operationally: when a circuit is down, the very first thing that happens is an argument about <em>which side</em> the fault is on, and every conversation you have with the facility and the carrier will be framed by it. "Is the light present at the demarc?" is the question that resolves it. If light is arriving at your panel and your router sees nothing, it is yours. If no light is arriving, it is theirs.</p>
<p>This is why "please check for light at the demarc panel and read me the port label" is one of the most valuable remote hands requests in existence. It is a five-minute task that assigns ownership of an outage.</p>
<h2 id="meet-me-room">Meet-Me Room</h2>
<p>The <strong>MMR</strong> is the building's switchboard: a secured room containing fibre distribution frames, patch panels and optical termination equipment, where every carrier, cloud provider and tenant in the facility lands their network.</p>
<p>You do not get access. Nobody gets casual access — the MMR is typically the most access-controlled space in the building, because it contains everyone's connectivity at once.</p>
<p>The property that makes a facility valuable is the <em>contents</em> of its MMR. A <strong>carrier-neutral</strong> facility is one that lets any carrier terminate there and does not force you to buy connectivity from the landlord. The number and identity of carriers, clouds and exchanges present in the MMR is, in practice, the main thing that distinguishes one datacenter from another once power and cooling are adequate. When someone says a facility is "well connected", that is what they mean.</p>
<h2 id="cross-connect">Cross-connect</h2>
<p>A <strong>cross-connect</strong> is a physical cable installed by the facility between two termination points in the building — nearly always from your demarc panel to some other party's termination in the MMR.</p>
<p>The things to internalise:</p>
<p><strong>It is a cable, not a circuit and not a service.</strong> There is no bandwidth associated with it. It carries whatever the two ends agree to run over it. Ordering a cross-connect and expecting connectivity is like ordering a patch lead and expecting an ISP.</p>
<p><strong>It has an install fee and a recurring monthly rental.</strong> Cross-connect rent is a meaningful line item, and it is why consolidating onto fewer, bigger connections is a real cost lever.</p>
<p><strong>It takes days, not minutes.</strong> A human has to route and patch it. Plan lead time into any project that needs one, and plan more if the facility is busy.</p>
<p><strong>It comes in flavours.</strong> Singlemode fibre is the default for anything modern. Multimode and copper exist, usually for legacy hand-offs. Specify which you need, because the far end dictates it.</p>
<p><strong>A and Z.</strong> The two ends of a circuit are the <strong>A-side</strong> and the <strong>Z-side</strong>. This is unrelated to the A/B of power from Part 2 — same letters, different domain. Context disambiguates: power has A and B, circuits have A and Z.</p>
<h2 id="loa-cfa-the-document-that-makes-it-happen">LOA-CFA, the document that makes it happen</h2>
<p>To order a cross-connect, you supply a <strong>Letter of Authorization / Connecting Facility Assignment</strong>.</p>
<p>The other party issues it. It says, in effect: <em>this customer is authorised to connect to us, and here is the exact cabinet, patch panel and port to land it on.</em> The facility will not patch anything without it, because they have no other proof the far end consented to being connected to.</p>
<p>A good LOA-CFA identifies the party, the location, the cabinet, the panel and the port. That precision is the point — a technician in the MMR needs to find one specific port among thousands.</p>
<p>Two operational details worth knowing. <strong>They expire.</strong> AWS, for example, issues an LOA-CFA for a Direct Connect port and the authority lapses if the cross-connect is not completed in time; you re-download it from the console to renew. And <strong>you will normally need two</strong>: one from the far end for their side, and often one from yourself authorising the facility to land it on your panel.</p>
<p>The realistic sequence for a cloud on-ramp:</p>
<ol>
<li>Request a port from the provider in their console. They allocate it and issue an <strong>LOA-CFA</strong>.</li>
<li>Download the LOA-CFA.</li>
<li>Open a cross-connect order with the facility, attaching the LOA-CFA, specifying your cabinet, your panel, your port, and the media type.</li>
<li>Facility schedules a technician. Days pass.</li>
<li>Cross-connect is installed. You confirm light and the provider confirms their side.</li>
<li>Configure the logical layer — VLAN, BGP session, addressing.</li>
</ol>
<p>Steps 1 to 5 are paperwork and waiting. Step 6 is the part that feels like engineering and takes twenty minutes. Budget accordingly, and start the paperwork long before you think you need to.</p>
<h2 id="direct-connect-to-a-cloud">Direct connect to a cloud</h2>
<p>A <strong>direct connection</strong> to a cloud provider is a cross-connect whose far end is the provider's router rather than a carrier's. The brand names: <strong>AWS Direct Connect</strong>, <strong>Azure ExpressRoute</strong>, <strong>Google Cloud Interconnect</strong>.</p>
<p>The benefit is that traffic to that cloud leaves your cabinet, crosses the MMR on your fibre, and lands on the provider without traversing the public internet. You get predictable latency, no exposure to internet-path weather, generally lower egress pricing, and a private path for things you would rather not route publicly.</p>
<p>Two purchasing models, and the difference matters:</p>
<p><strong>Dedicated.</strong> An entire physical port that is yours. In AWS's case 1, 10, 100 or 400 Gbps. You order the cross-connect yourself, using the LOA-CFA process above. You get full control, the option of MACsec encryption on your own port, and the highest availability commitments. You also need to be physically present in a facility where the provider has a presence.</p>
<p><strong>Hosted.</strong> A logical slice of a partner's existing port, sold to you in much smaller increments — AWS hosted connections run from 50 Mbps up to 25 Gbps. The partner already has the cross-connect, so you order none. It is faster to provision, available at sizes a dedicated port cannot offer, and it is the right answer if you need 500 Mbps rather than 10 Gbps.</p>
<p>Choose dedicated if you need the bandwidth, the control or the encryption. Choose hosted if you need a modest amount of private connectivity quickly, or if you are not in the right building.</p>
<p><strong>Redundancy here is a physical question, not a logical one.</strong> Two connections that share a cross-connect, a panel, a room or a building are not redundant. If the direct connection matters, the second one should land on a different provider device, ideally in a different facility, and you should be able to say which physical elements the two paths share. Ask specifically; "we have two connections" is not an answer.</p>
<h2 id="getting-to-the-internet-transit-peering-ix">Getting to the internet: transit, peering, IX</h2>
<p>Three distinct things, frequently muddled.</p>
<p><strong>IP transit</strong> is the commercial service of "reach the entire internet through me". You buy it from a carrier, you announce your prefixes to them over BGP, and they carry your traffic anywhere. It is the default and it is what most people mean by "our internet connection".</p>
<p><strong>Peering</strong> is a direct BGP relationship with another network, over which you exchange <em>only</em> traffic destined for each other's customers. It is not a route to the internet — it is a shortcut to that specific network. Because both sides benefit, peering is settlement-free in the overwhelming majority of cases.</p>
<p><strong>An Internet Exchange (IX)</strong> is shared infrastructure — a large layer-2 switching fabric in a colocation facility — where many networks connect once and then peer with many others. You take one cross-connect to the IX, and from that single port you can establish BGP sessions with hundreds of networks. That leverage is the entire value proposition.</p>
<pre data-lang-none><code>   TRANSIT                     PEERING                 IX
   ───────                     ───────                 ──
   you ──► carrier ──► the     you ◄──► them           you ──┐
           (paid, reaches      (direct, usually        them ─┤ IX
            everywhere)         free, only their       more ─┤ fabric
                                own customers)         more ─┘
                                                       one cross-connect,
                                                       many BGP sessions
</code></pre>
<p>In practice a mature network buys transit from <strong>two</strong> carriers for full reachability and resilience, and peers at an IX to move the large, cheap, latency-sensitive volume — which is why an IX presence pays for itself once your traffic is big enough.</p>
<p><strong>Billing is worth understanding because it is unlike anything else you buy.</strong> IP transit is normally billed on the <strong>95th percentile</strong>: usage is sampled at five-minute intervals across the month, the samples are sorted, the top 5 % are discarded, and you pay on the highest remaining sample. Roughly the top 36 hours of every month are free.</p>
<p>The consequences are genuinely useful. <strong>Short bursts are free</strong> — a release, a backup window, a traffic spike, provided it stays under about 36 hours in aggregate. <strong>Sustained increases cost immediately.</strong> And a <strong>commit</strong> is a minimum monthly volume you agree to pay for regardless of use, in exchange for a lower per-megabit rate; a common shape is a commit around 10 % of port capacity. So a large port with a small commit is a legitimate way to buy burst headroom cheaply, and scheduling bulk transfers so they do not extend your 95th percentile is a real and effective cost optimisation.</p>
<h2 id="what-to-know-about-your-own-connectivity">What to know about your own connectivity</h2>
<p>Five questions. If you cannot answer them, that is your homework, and every one of them is answerable by asking rather than by touching anything.</p>
<ol>
<li><strong>How many cross-connects leave your cage, and where does each one terminate?</strong></li>
<li><strong>For each, which physical panel and port is it on at your end?</strong> This is what you quote in a ticket.</li>
<li><strong>Which are transit, which are peering, which are cloud on-ramps?</strong></li>
<li><strong>What do your two "redundant" paths physically share?</strong> Same panel? Same room? Same building? Same conduit into the street?</li>
<li><strong>What is your 95th percentile, and what is your commit?</strong></li>
</ol>
<p>Question 4 is the one that catches people. Diversity on paper is common; diversity in conduit is rare and expensive; the difference only becomes visible when a digger finds the one duct both circuits happened to use.</p>
<h2 id="the-one-line-summary">The one-line summary</h2>
<p><strong>Demarc</strong> is where your responsibility ends. The <strong>MMR</strong> is the building's switchboard and its tenant list is what makes a facility valuable. A <strong>cross-connect</strong> is a rented physical cable, ordered with an <strong>LOA-CFA</strong> from the far end, and it takes days. <strong>Direct connect</strong> is a cross-connect to a cloud — dedicated for a whole port, hosted for a slice. <strong>Transit</strong> reaches everywhere and is billed at the <strong>95th percentile</strong>; <strong>peering</strong> is a free shortcut to one network; an <strong>IX</strong> is one cross-connect that gets you hundreds of them.</p>
<p>Next up: <strong>Part 7 — directing remote hands</strong>, which is where all six previous parts turn into a ticket that works the first time.</p>]]></content:encoded>
  </item>
  <item>
    <title>Data Center Part 5: Where the switches live</title>
    <link>https://justmeandlinux.com/dc-05-where-the-switches-live/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/dc-05-where-the-switches-live/</guid>
    <pubDate>Fri, 21 Aug 2026 12:00:00 GMT</pubDate>
    <category>Linux</category>
    <description>Your server&apos;s NIC cable probably travels about two feet. That fact shapes more of the datacenter than anything else in this series. This part is where switches physically sit, why that choice was made, and what it means for the cabling in your cabinet. The…</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<p>Your server's NIC cable probably travels about two feet. That fact shapes more of the datacenter than anything else in this series.</p>
<p>This part is where switches physically sit, why that choice was made, and what it means for the cabling in your cabinet.</p>
<h2 id="the-three-placements">The three placements</h2>
<p>There are three ways to answer "where does the server plug in", and they are named after where the switch is.</p>
<p><strong>Top of Rack (ToR).</strong> A switch in each cabinet, usually at the top, serving only the servers in that cabinet. Server-to-switch links are short DAC runs inside the cabinet. The switch's uplinks leave the cabinet on fibre.</p>
<p><strong>End of Row (EoR).</strong> One or two large chassis switches in a dedicated cabinet at the end of a row, serving every server in that row. Every server has a long cable running from its cabinet, through overhead or underfloor trays, to that cabinet.</p>
<p><strong>Middle of Row (MoR).</strong> Identical to EoR but positioned in the centre of the row, which halves the worst-case cable length.</p>
<pre data-lang-none><code>  TOP OF RACK                      END OF ROW
  ───────────                      ──────────
  ┌────┐ ┌────┐ ┌────┐             ┌────┐ ┌────┐ ┌────┐ ┌──────┐
  │ SW │ │ SW │ │ SW │             │    │ │    │ │    │ │ ████ │  ← chassis
  ├────┤ ├────┤ ├────┤             │srv │ │srv │ │srv │ │ ████ │    switches
  │srv │ │srv │ │srv │             │srv │ │srv │ │srv │ │ ████ │
  │srv │ │srv │ │srv │             │srv │ │srv │ │srv │ │ ████ │
  │srv │ │srv │ │srv │             │srv │ │srv │ │srv │ │      │
  └─┬──┘ └─┬──┘ └─┬──┘             └─┬──┘ └─┬──┘ └─┬──┘ └───▲──┘
    │      │      │                  └──────┴──────┴────────┘
    └──────┴──────┴─► spine          long structured cabling runs
    short DAC inside                 (Cat6A or fibre, through trays,
    each cabinet; only               via patch panels at both ends)
    fibre uplinks leave
</code></pre>
<p><strong>ToR won, for most people, for practical rather than architectural reasons.</strong> Cabling inside a cabinet is cheap, short, and done once when the cabinet is built. Cabling from every cabinet to a row-end chassis is expensive, has to be planned before anything is installed, and fills trays. ToR also means a new cabinet is a self-contained unit you can build and test in isolation.</p>
<p><strong>EoR and MoR still make sense</strong> in three cases: when you have too few servers per cabinet for a whole switch to be worthwhile, when you want server-to-server traffic within a row to stay inside one switch (which removes a hop and can remove oversubscription entirely), and when you have standardised on structured Cat6A cabling and 10GBASE-T. A single big switch is also one thing to manage rather than forty.</p>
<p>A note on the name: a "top of rack" switch is not required to be at the top. Placing it at the top is the convention because that is where cable trays and vertical managers converge, but you will see them mid-cabinet in high-density layouts to halve the average cable run to servers above and below.</p>
<h2 id="what-a-tor-cabinet-actually-contains">What a ToR cabinet actually contains</h2>
<p>Working down from the top of a typical cabinet:</p>
<pre data-lang-none><code>  U42  ░ blanking ░
  U41  patch panel   ← fibre to the MMR / cross-connects (Part 6)
  U40  sw-01         ← ToR switch A   48x25G SFP28 + 8x100G QSFP28
  U39  sw-02         ← ToR switch B   (the redundant pair)
  U38  mgmt-sw-01    ← 1G copper switch for BMC / out-of-band (Part 4)
  U37  ░ blanking ░
  U36  srv-01
  U35  srv-02
   ..  ...
</code></pre>
<p>Four things in that stack are worth naming.</p>
<p><strong>Two ToR switches, not one.</strong> Each server has one link to each switch. Lose a switch — or reboot one for a firmware upgrade — and every server stays up on the other. A cabinet with a single ToR switch has a single point of failure that takes out forty machines at once, which is a decision you can make deliberately for a stateless tier and should never make by accident.</p>
<p><strong>A separate management switch.</strong> Cheap, 1 Gb, copper, carrying only BMC and console traffic on its own VLAN. It exists so that your out-of-band path does not depend on the production switches you might be trying to fix. This is Part 4's argument expressed in hardware.</p>
<p><strong>A patch panel.</strong> Structured fibre terminates here rather than plugging directly into the switch, so that when a switch is replaced the building's cabling is not disturbed. Anything leaving the cabinet permanently should pass through it.</p>
<p><strong>Blanking panels above and below the active kit</strong>, for the airflow reason from Part 1.</p>
<p>One airflow detail specific to switches, because it bites people: <strong>switches are built in two airflow directions.</strong> <em>Port-side exhaust</em> (also called front-to-back relative to the ports) and <em>port-side intake</em> (back-to-front). Since a switch's ports face the <em>rear</em> of the cabinet where the servers' cables are, but the cold aisle is at the <em>front</em>, you need the variant that pulls cold air from the front and exhausts out the rear like the servers do. Order the wrong one and you have installed a device that inhales its neighbours' exhaust. Vendors sell both and the part numbers differ by a suffix.</p>
<h2 id="leaf-and-spine">Leaf and spine</h2>
<p>Above the cabinet, modern datacenter networks are almost universally <strong>leaf-spine</strong>, also called a Clos fabric or a fat tree.</p>
<p>Every <strong>leaf</strong> — in practice, every ToR switch — connects to <strong>every spine</strong> switch. Leaves never connect to each other. Spines never connect to each other. Servers connect only to leaves.</p>
<pre data-lang-none><code>            ┌────────┐   ┌────────┐   ┌────────┐   ┌────────┐
   SPINE    │  sp-1  │   │  sp-2  │   │  sp-3  │   │  sp-4  │
            └─┬─┬─┬─┬┘   └┬─┬─┬─┬─┘   └┬─┬─┬─┬─┘   └┬─┬─┬─┬─┘
              │ │ │ └──────┼─┼─┼─┼──────┼─┼─┼─┼──────┘ │ │ │
              │ │ └────────┼─┼─┼─┼──────┘ │ │ │        │ │ │
              ... every leaf connects to every spine ...
            ┌─┴─────┐   ┌──┴────┐   ┌──┴────┐   ┌──┴────┐
   LEAF     │ leaf1 │   │ leaf2 │   │ leaf3 │   │ leaf4 │
            └───┬───┘   └───┬───┘   └───┬───┘   └───┬───┘
              servers     servers     servers     servers
</code></pre>
<p>The property that matters: <strong>every server is exactly the same number of hops from every other server</strong> — leaf, spine, leaf. Three hops, always, whether the destination is in the next cabinet or across the hall. Latency becomes predictable, and capacity scales by adding spines rather than by replacing a bigger box with an even bigger box.</p>
<p>This replaced the old three-tier access/aggregation/core design, which was built for traffic that mostly flowed <em>north-south</em> — in and out of the datacenter. Modern workloads are overwhelmingly <strong>east-west</strong>: service to service, node to node, replica to replica. Leaf-spine is the shape you build when most of your traffic never leaves the building.</p>
<h2 id="oversubscription-and-the-number-to-ask-for">Oversubscription, and the number to ask for</h2>
<p>Your ToR switch has, say, 48 ports of 25 Gb facing the servers and 8 ports of 100 Gb facing the spines.</p>
<ul>
<li>Downstream: 48 × 25 = <strong>1200 Gb/s</strong></li>
<li>Upstream: 8 × 100 = <strong>800 Gb/s</strong></li>
</ul>
<p>That is an <strong>oversubscription ratio</strong> of 1200:800, or <strong>1.5:1</strong>. If every server in the cabinet transmitted at full line rate simultaneously, the uplinks could not carry it.</p>
<p><strong>This is deliberate and normal.</strong> Uplink optics are expensive, and it is vanishingly unlikely that forty-eight machines all saturate their NICs in the same millisecond. A 3:1 ratio is common and unremarkable; 2:1 is comfortable; 1:1 (non-blocking) is bought only for workloads that genuinely need it — distributed storage rebuilds, HPC, large-scale training.</p>
<p><strong>Why you should care:</strong> oversubscription is where "the network is slow" comes from when every individual link looks healthy. Your NIC shows 25 Gb, the switch port shows no errors, and yet throughput collapses during the nightly backup — because sixteen other machines in the cabinet are also backing up and you are all sharing 800 Gb of uplink.</p>
<p>So: <strong>know your cabinet's oversubscription ratio</strong>, and know it as a number. It is a fair question to ask whoever runs the fabric, and if the answer is "we don't know", that is itself the finding. Then graph uplink utilisation, not just server NIC utilisation. The server-side graph will look fine right up until it does not.</p>
<h2 id="how-your-server-attaches">How your server attaches</h2>
<p>Two links, one to each ToR switch. What happens on top of them is a Linux question, and it is the one part of this series that lands squarely in your territory.</p>
<p><strong>Active/backup bonding</strong> (<code>mode=1</code>) is the simple option. One link carries traffic, the other stands by. It needs nothing from the switches, works with any two switches anywhere, and costs you half your bandwidth.</p>
<p><strong>LACP</strong> (<code>mode=4</code>, 802.3ad) uses both links at once. It requires both switch ports to be in the same link aggregation group — which, when the ports are on two <em>different</em> physical switches, requires those switches to pretend to be one. That is <strong>MLAG</strong> (multi-chassis link aggregation): Arista's MLAG, Cisco's vPC, and vendor equivalents. The pair keeps state in sync over a peer link and presents a single logical switch to your server.</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># what your NIC actually negotiated — check this before blaming the fabric</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ethtool</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eno1</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -E</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'Speed|Duplex|Link detected'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># bond state, including which slave is active and whether LACP came up</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">cat</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /proc/net/bonding/bond0</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># who is on the other end — LLDP is the single most useful command here</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">lldpctl</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                        # or: lldpcli show neighbors detail</span></span>
<span class="line"></span></code></pre>
<p><strong>Learn <code>lldpctl</code>.</strong> With LLDP running on the switches, it tells you the neighbour switch's name, the exact port your cable is in, and often the port description. That turns "which switch port is this server in" from a remote hands ticket with a torch into one command. When you are writing a ticket about a specific link, <code>lldpctl</code> output is the authoritative identifier to quote.</p>
<p>A third option worth knowing about: <strong>EVPN/VXLAN with a routed fabric</strong>, where the leaf is a layer-3 boundary and there is no MLAG at all — each server link is a separate routed path, and the host runs BGP. It is cleaner and increasingly common at scale, and it changes your side of the picture from bonding to routing.</p>
<h2 id="where-oversubscription-meets-your-cabling">Where oversubscription meets your cabling</h2>
<p>One last practical link back to Part 3. The uplinks from a ToR switch to the spines are the runs that leave the cabinet, which means they are fibre, through the patch panel, and they are the reason your cabinet has a fibre panel at all. Server links stay inside the cabinet, which is why they are DAC.</p>
<p>So the cabling in a cabinet has a shape you can now predict entirely:</p>
<ul>
<li><strong>Inside the cabinet, rear</strong>: forty short DAC cables, servers to the two ToR switches.</li>
<li><strong>Inside the cabinet, rear</strong>: forty short Cat6A leads, BMCs to the management switch.</li>
<li><strong>Leaving the cabinet</strong>: a handful of fibre uplinks through the patch panel, and two power whips.</li>
</ul>
<p>Which means: <strong>most of what looks like a wall of cables is short, local and identical, and only a few strands actually go anywhere.</strong> That was the thing that made the rear of a cabinet stop looking like chaos to me.</p>
<h2 id="the-one-line-summary">The one-line summary</h2>
<p><strong>Top of rack won</strong>: short DAC inside the cabinet, fibre uplinks out, two switches per cabinet plus a cheap 1 Gb management switch. Above that, <strong>leaf-spine</strong> makes every server three hops from every other. <strong>Know your oversubscription ratio and graph your uplinks</strong>, because that is where unexplained slowness lives. And run LLDP, so you never have to ask a human which port a cable is in.</p>
<p>Next up: <strong>Part 6 — leaving the cage</strong>, where the demarc, the Meet-Me Room and the cross-connect finally connect to each other.</p>]]></content:encoded>
  </item>
  <item>
    <title>Data Center Part 4: Out-of-band access</title>
    <link>https://justmeandlinux.com/dc-04-out-of-band/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/dc-04-out-of-band/</guid>
    <pubDate>Fri, 21 Aug 2026 11:00:00 GMT</pubDate>
    <category>Linux</category>
    <description>Every remote hands ticket you do not have to open is a win, and out-of-band access is how you avoid most of them. There is a second computer inside your server. It has its own CPU, its own firmware, its own network stack and its own IP address, it runs on…</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<p>Every remote hands ticket you do not have to open is a win, and out-of-band access is how you avoid most of them.</p>
<p>There is a second computer inside your server. It has its own CPU, its own firmware, its own network stack and its own IP address, it runs on standby power whenever the machine is plugged in, and it does not care whether the operating system is running, halted, or absent. Understanding it properly is the single highest-leverage thing in this series.</p>
<h2 id="bmc-and-its-four-brand-names">BMC, and its four brand names</h2>
<p>The chip is a <strong>BMC</strong> — Baseboard Management Controller. In practice it is usually an ASPEED AST2500 or AST2600 soldered to the motherboard, running a small embedded Linux of its own.</p>
<p>Every vendor brands it differently, which is why one thing has four names:</p>
<table>
<thead>
<tr>
<th>Vendor</th>
<th>Brand</th>
</tr>
</thead>
<tbody>
<tr>
<td>Dell</td>
<td><strong>iDRAC</strong></td>
</tr>
<tr>
<td>HPE</td>
<td><strong>iLO</strong></td>
</tr>
<tr>
<td>Lenovo</td>
<td><strong>XCC</strong> (formerly IMM)</td>
</tr>
<tr>
<td>Supermicro</td>
<td>usually just <strong>IPMI</strong></td>
</tr>
<tr>
<td>Cisco UCS</td>
<td><strong>CIMC</strong></td>
</tr>
</tbody>
</table>
<p>They are the same category of thing. When someone says iDRAC and you run HPE, mentally substitute iLO and carry on.</p>
<p><strong>IPMI</strong> is the old wire protocol for talking to a BMC — a UDP-based standard from the late nineties, at version 2.0 since 2004. <strong>Redfish</strong> is its modern replacement: HTTPS, JSON, REST, sane authentication. New tooling should use Redfish. Old tooling and every scrap of documentation on the internet uses <code>ipmitool</code>, so you need both.</p>
<h2 id="what-it-can-actually-do">What it can actually do</h2>
<p>The list is longer than most people assume, and each item is a ticket you did not open.</p>
<ul>
<li><strong>Power on, power off, hard reset, and graceful shutdown</strong> — independent of the OS.</li>
<li><strong>Serial-over-LAN</strong>: the machine's console, including the bootloader and kernel messages, over the network.</li>
<li><strong>Virtual media</strong>: mount an ISO from your laptop as if it were a physical drive, and reinstall the OS.</li>
<li><strong>Boot device override</strong>: "next boot, come up on PXE" without touching BIOS by hand.</li>
<li><strong>Full sensor telemetry</strong>: every temperature, fan RPM, voltage and PSU status in the chassis.</li>
<li><strong>The System Event Log (SEL)</strong>: a hardware event history that survives OS reinstalls and often contains the actual cause of a mystery reboot.</li>
<li><strong>The identify LED</strong>: light a blue LED on the front and rear of the chassis on demand.</li>
</ul>
<p>That last one deserves its own paragraph, because it is the best remote hands trick in this whole series. Instead of writing "the third server from the top, I think, the one with the blue sticker", you light the LED and write <strong>"the machine with the flashing blue ID LED, at U37"</strong>. The technician cannot pick the wrong box. It converts an ambiguous instruction into an unambiguous one, for free.</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># light it</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ipmitool</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -I</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> lanplus</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -H</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 10.20.0.37</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -U</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> admin</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -P</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">$PW</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> chassis</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> identify</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 300</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">   # 300 seconds</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># turn it off again</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ipmitool</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -I</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> lanplus</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -H</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 10.20.0.37</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -U</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> admin</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -P</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">$PW</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> chassis</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> identify</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span></span>
<span class="line"></span></code></pre>
<h2 id="the-commands-worth-knowing">The commands worth knowing</h2>
<p><code>ipmitool</code> is in every distro's repos. <code>-I lanplus</code> selects IPMI 2.0 over the network and is required for essentially everything remote.</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">BMC</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"-I lanplus -H 10.20.0.37 -U admin -P </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">$PW</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ipmitool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $BMC </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">chassis</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> power</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> status</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">      # on or off</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ipmitool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $BMC </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">chassis</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> power</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> on</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ipmitool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $BMC </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">chassis</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> power</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> off</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">         # abrupt — like holding the button</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ipmitool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $BMC </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">chassis</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> power</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> soft</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        # ACPI graceful shutdown</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ipmitool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $BMC </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">chassis</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> power</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> cycle</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">       # off, pause, on</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ipmitool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $BMC </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">chassis</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> power</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> reset</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">       # hard reset, no power removal</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ipmitool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $BMC </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">sel</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> elist</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                 # hardware event log, human-readable</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ipmitool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $BMC </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">sel</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> clear</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ipmitool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $BMC </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">sdr</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> list</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                  # every sensor and its reading</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ipmitool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $BMC </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">sensor</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> list</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ipmitool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $BMC </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">lan</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> print</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">               # the BMC's own IP, MAC, VLAN, gateway</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ipmitool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $BMC </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">chassis</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> bootdev</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> pxe</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">       # override next boot</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ipmitool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $BMC </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">sol</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> activate</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">              # attach to the serial console</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ipmitool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $BMC </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">mc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> reset</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> cold</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">             # reboot the BMC itself, not the server</span></span>
<span class="line"></span></code></pre>
<p>Two of those are worth calling out. <strong><code>sel elist</code> is the first thing to run after an unexplained reboot</strong> — a machine that vanished without a trace in the OS logs will frequently have a clean, dated entry in the SEL saying it overheated or lost a PSU. And <strong><code>mc reset cold</code> reboots the BMC without touching the running server</strong>, which is the fix when the BMC's web UI has wedged but the workload is fine. That distinction matters: you can repair your management path without an outage.</p>
<p>To escape from <code>sol activate</code>, the sequence is <code>~.</code> — the same escape as ssh. Which means if you are inside ssh <em>and</em> inside SOL, you need <code>~~.</code> to leave only the inner one. This catches everyone once.</p>
<p>The Redfish equivalent, for anything new:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -sk</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -u</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "admin:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">$PW</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> https://10.20.0.37/redfish/v1/Systems/1</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> jq</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> .PowerState</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -sk</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -u</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "admin:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">$PW</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -X</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> POST</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  -H</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'Content-Type: application/json'</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  -d</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '{"ResetType":"ForceRestart"}'</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  https://10.20.0.37/redfish/v1/Systems/1/Actions/ComputerSystem.Reset</span></span>
<span class="line"></span></code></pre>
<h2 id="dedicated-versus-shared-the-cable-question">Dedicated versus shared: the cable question</h2>
<p>This is the thing you asked about directly, and it is a real decision with a real consequence.</p>
<p>On the back of the server there is usually a small RJ45 physically separate from the data NICs, labelled <em>BMC</em>, <em>IPMI</em>, <em>iDRAC</em>, <em>iLO</em> or <em>MGMT</em>. That is the <strong>dedicated</strong> management port. It is almost always 1 Gb copper, and it is wired to a separate management switch.</p>
<p>The alternative is <strong>shared</strong> mode — also called <strong>LOM</strong> (LAN on Motherboard) or <strong>NC-SI</strong>, which is the sideband protocol that lets the BMC borrow one of the main NICs. One physical cable carries both your production traffic and your management traffic, usually separated by VLAN.</p>
<pre data-lang-none><code>   DEDICATED                              SHARED / NC-SI
   ┌─────────────────────┐                ┌─────────────────────┐
   │ server              │                │ server              │
   │  ┌──────┐           │                │  ┌──────┐           │
   │  │ BMC  ├── mgmt ───┼──► mgmt switch │  │ BMC  ├──┐        │
   │  └──────┘   RJ45    │                │  └──────┘  │ NC-SI  │
   │  ┌──────┐           │                │  ┌──────┐  │        │
   │  │ NIC  ├── data ───┼──► ToR switch  │  │ NIC  │◄─┘        │
   │  └──────┘           │                │  └──┬───┘           │
   └─────────────────────┘                └─────┼───────────────┘
                                                └── data + mgmt ──► ToR
   two cables, two failure domains         one cable, one failure domain
</code></pre>
<p><strong>The argument for dedicated is the entire point of out-of-band.</strong> If the BMC shares a NIC with the OS, then a failed NIC, a bad driver, a misconfigured bond, an unplugged cable or a switch port error-disabling takes out <em>both</em> your production path and the thing you were going to use to fix it. You have built out-of-band management that goes out of band at exactly the moment you need it.</p>
<p><strong>The argument for shared is cost</strong>: half the cables, half the switch ports, less structured cabling. In a large fleet that is real money.</p>
<p>My view, stated as a view: <strong>use the dedicated port.</strong> The saving is one RJ45 patch lead and one port on a cheap 1 Gb switch, and what you buy with it is that your recovery path does not share a failure domain with the thing that breaks. That is the whole product. If budget forces shared mode, at least know that you have made that trade, and make sure something else — a serial console server, or a switched PDU — gives you an independent path.</p>
<p>There is a third setting on many boards, <strong>failover</strong>, where the BMC prefers the dedicated port and falls back to shared if the link drops. It sounds ideal and is occasionally surprising, because the BMC can silently move to a different network and a different address. If you use it, know it is on.</p>
<h2 id="never-ever-expose-a-bmc">Never, ever expose a BMC</h2>
<p>This is not a stylistic preference. It is the one hard security rule in this series.</p>
<p>A BMC is a privileged computer with full physical-equivalent control of your server, running vendor firmware that is patched slowly and often not at all, sitting <em>underneath</em> the operating system. An attacker who owns the BMC owns the machine in a way that survives an OS reinstall, and no host-based control can see them.</p>
<p>The protocol makes it worse. <strong>IPMI 2.0's RAKP key exchange hands out a password hash to anyone who asks for a valid username, before authentication.</strong> That is not a bug in an implementation — it is how the protocol was specified, and it means an exposed BMC is an offline cracking target. Separately, <strong>cipher suite 0</strong> is an option that disables authentication entirely: supply a valid username, no password, get in. Historical scans found it enabled by default on a majority of internet-facing BMCs.</p>
<p>Research published this year found tens of thousands of BMCs still reachable from the internet, with a large fraction of them still handing over crackable hashes to unauthenticated requests. The problem has not aged out. It got bigger, because the fleet got bigger.</p>
<p>So, the rules:</p>
<ol>
<li><strong>BMCs live on a dedicated management VLAN with no route to the internet.</strong> Not a firewall rule on a shared VLAN. A separate segment.</li>
<li><strong>Reach them through a jump host or a VPN</strong>, never directly.</li>
<li><strong>Disable cipher suite 0.</strong> Check with <code>ipmitool ... lan print 1</code> and look at the cipher suite list.</li>
<li><strong>Change the default credentials.</strong> Supermicro's <code>ADMIN</code>/<code>ADMIN</code> and equivalents are the first thing anyone tries.</li>
<li><strong>Prefer Redfish over IPMI</strong> where the hardware supports it, and turn IPMI-over-LAN off entirely if nothing needs it.</li>
<li><strong>Patch BMC firmware.</strong> It is the piece of software in your estate least likely to have been updated and most likely to be catastrophic.</li>
</ol>
<p>CISA's guidance on this is blunt and worth quoting in spirit: use a physically separate management network where you can, default-deny, and permit management access only from dedicated administrative systems.</p>
<h2 id="the-serial-console-which-still-matters">The serial console, which still matters</h2>
<p>SOL over IPMI is convenient but it depends on the BMC being alive and on the network. A <strong>console server</strong> (sometimes a terminal server) is the independent alternative: a box with 16, 32 or 48 serial ports, cabled to the serial port of every device in the row, reachable over the management network.</p>
<p>It is old technology and it is the thing that saves you when everything else is gone, because the serial console works when the network stack is dead, when the machine is stuck in the bootloader, and when a firmware update has bricked the BMC. It is also the only way to manage a lot of network equipment during an upgrade gone wrong.</p>
<p>Cabling vocabulary you will meet: many devices use <strong>RJ45 serial</strong> with the Cisco-standard <strong>rollover</strong> pinout, and the light blue flat cable is a near-universal visual convention. Others use <strong>DB9</strong>, and newer servers increasingly use <strong>USB</strong> or a micro-USB service port on the front bezel.</p>
<p>On the Linux side, none of this is exotic:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># make sure the kernel actually talks to the serial port — otherwise the</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># console server shows you a blank screen and you blame the hardware</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># /etc/default/grub:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">GRUB_CMDLINE_LINUX</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"console=tty0 console=ttyS0,115200n8"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">GRUB_TERMINAL</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"console serial"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">GRUB_SERIAL_COMMAND</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"serial --speed=115200 --unit=0"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># then</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">update-grub</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">          # or grub2-mkconfig -o /boot/grub2/grub.cfg</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">systemctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> enable</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --now</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> serial-getty@ttyS0.service</span></span>
<span class="line"></span></code></pre>
<p>That last pair is worth doing on every machine you own, in advance. A serial console you configured after the outage started is not a serial console.</p>
<h2 id="three-different-things-reboot-it-can-mean">Three different things "reboot it" can mean</h2>
<p>This is the escalation ladder, and knowing which rung you are on stops you from asking for the wrong thing.</p>
<pre data-lang-none><code>  1. OS-level         ssh host reboot
     ─────────        Clean. Needs a working OS and network.

  2. BMC-level        ipmitool ... chassis power cycle
     ──────────       Works when the OS is dead. Needs a reachable BMC.
                      NOTE: does NOT remove standby power. The BMC keeps
                      running, and so does anything else on standby.

  3. Power-level      switched PDU: outlet off, wait, outlet on
     ────────────     Works when the BMC is dead. Removes standby power
                      entirely, which is what actually clears a wedged BMC
                      or a confused PSU. Both cords for a dual-corded box.

  4. Human            remote hands ticket.
     ──────           Everything else has failed, or the fault is physical.
</code></pre>
<p><strong>The gap between rung 2 and rung 3 is the one people miss.</strong> <code>chassis power cycle</code> does not de-energise the board — the BMC and standby rails stay up. When a machine is in a genuinely stuck state, or when the BMC itself has stopped answering, you need real power removal. That is what a <strong>switched PDU</strong> buys you, and it is why Part 2 argued for switched-and-monitored units. Without one, rung 3 is a ticket.</p>
<p>If you are cutting power at the PDU on a dual-corded server, <strong>both outlets must go off</strong>, and a real drain needs perhaps thirty seconds rather than two. Half a power-cycle on a redundant machine is a no-op that looks like a failed fix.</p>
<h2 id="what-to-have-in-place-before-you-need-it">What to have in place before you need it</h2>
<p>A short checklist. Each of these is boring to do in advance and impossible to do during an incident.</p>
<ul>
<li>Every BMC has a <strong>static or reserved address</strong>, is in DNS, and is in your documentation next to its server's name and U position.</li>
<li>Every BMC is on the <strong>management VLAN</strong>, with <strong>default credentials changed</strong> and <strong>cipher 0 disabled</strong>.</li>
<li><strong>Serial console configured in GRUB and a getty enabled</strong> on every host.</li>
<li><strong>Switched PDU</strong>, with outlet-to-server mapping documented — including which outlet is A and which is B for each machine.</li>
<li>BMC reachability is <strong>monitored</strong>. A BMC that died three months ago is discovered at the worst possible moment otherwise. It is an ICMP check and an authenticated Redfish poll; both are cheap.</li>
<li><strong>Optical Rx power and PSU status scraped into metrics</strong>, so degradation shows up as a trend rather than an outage.</li>
</ul>
<h2 id="the-one-line-summary">The one-line summary</h2>
<p>The BMC is a separate computer that can power, console and reinstall your server without the OS. <strong>Give it its own port, its own VLAN, and no path to the internet.</strong> Learn <code>sel elist</code> for post-mortems and <code>chassis identify</code> for tickets. Keep a switched PDU, because a BMC-level power cycle does not remove standby power and a wedged BMC does not answer.</p>
<p>Next up: <strong>Part 5 — where the switches live</strong>, and why your server's NIC probably terminates two feet above it.</p>]]></content:encoded>
  </item>
  <item>
    <title>Data Center Part 3: Cables, optics and transceivers</title>
    <link>https://justmeandlinux.com/dc-03-cables-and-optics/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/dc-03-cables-and-optics/</guid>
    <pubDate>Fri, 21 Aug 2026 10:00:00 GMT</pubDate>
    <category>Linux</category>
    <description>&quot;Send me a 3-metre DAC&quot; is a complete, unambiguous, orderable request. &quot;Send me a network cable&quot; is not. This part is about being able to say the first thing. The three ways to connect two ports Every high-speed link in a datacenter is one of three physical…</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<p>"Send me a 3-metre DAC" is a complete, unambiguous, orderable request. "Send me a network cable" is not. This part is about being able to say the first thing.</p>
<h2 id="the-three-ways-to-connect-two-ports">The three ways to connect two ports</h2>
<p>Every high-speed link in a datacenter is one of three physical things, and the choice is almost entirely about distance.</p>
<p><strong>DAC — Direct Attach Copper.</strong> A fixed length of twinaxial copper with the transceiver moulded permanently onto both ends. One part number, one item, no separate cable. Cheapest, lowest power, lowest latency. Also the shortest.</p>
<p><strong>AOC — Active Optical Cable.</strong> Same idea — ends permanently attached — but the middle is fibre and the ends contain lasers. Longer reach, more money, more power draw, and still a single sealed unit you cannot repair.</p>
<p><strong>Transceiver plus fibre.</strong> Two removable modules plus a separate fibre patch lead. The most expensive per link and the most flexible: you can change the fibre without changing the optics, run through patch panels, and reach anything from metres to kilometres.</p>
<pre data-lang-none><code>  DAC                 [==transceiver==]━━━━copper━━━━[==transceiver==]
                       (moulded on)                    (moulded on)
                       ← up to ~3-7 m passive, ~10 m active →

  AOC                 [==transceiver==]────fibre────[==transceiver==]
                       (moulded on)                   (moulded on)
                       ← up to ~100 m →

  Transceiver+fibre   [module] ◄─┐                 ┌─► [module]
                                 └── fibre lead ───┘
                       ← metres to tens of kilometres →
                        (and the only one that survives a patch panel)
</code></pre>
<h2 id="how-far-each-one-goes">How far each one goes</h2>
<p>These are the numbers worth memorising, because they decide the answer nine times out of ten.</p>
<p><strong>Passive DAC</strong> is typically good to about <strong>3 m</strong>, with 5 m and sometimes 7 m available depending on speed and vendor. It contains no electronics at all — it is wire.</p>
<p><strong>Active DAC</strong> puts signal conditioning in the ends and reaches roughly <strong>5 to 10 m</strong>.</p>
<p><strong>AOC</strong> reaches up to about <strong>100 m</strong> over the multimode fibre inside it.</p>
<p>Notice how neatly that maps onto physical layout. Inside one cabinet, DAC. Between adjacent cabinets, active DAC or AOC. Across the room or between rooms, transceivers and structured fibre. The cable type is a proxy for "how far away is the other end", and if someone tells you the link type you can infer the topology.</p>
<p><strong>The trap: DAC lengths are exact and unforgiving.</strong> You are buying a specific physical length with the ends attached. Order 1 m when you needed 2 m and it does not reach; order 5 m when you needed 2 m and you have three metres of stiff twinax to hide in a cabinet that has nowhere to put it. Twinax is thick and does not bend tightly. Measure the real cable path — down the rack, through the manager, back up — not the straight-line distance.</p>
<h2 id="form-factors-which-encode-speed">Form factors, which encode speed</h2>
<p>The connector shape tells you the speed class. This is the whole naming system.</p>
<table>
<thead>
<tr>
<th>Form factor</th>
<th>Speed</th>
<th>Typical use</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>SFP</strong></td>
<td>1 Gb</td>
<td>legacy, out-of-band, management switches</td>
</tr>
<tr>
<td><strong>SFP+</strong></td>
<td>10 Gb</td>
<td>the long-time server workhorse</td>
</tr>
<tr>
<td><strong>SFP28</strong></td>
<td>25 Gb</td>
<td>the current server default</td>
</tr>
<tr>
<td><strong>QSFP+</strong></td>
<td>40 Gb</td>
<td>older uplinks</td>
</tr>
<tr>
<td><strong>QSFP28</strong></td>
<td>100 Gb</td>
<td>the current uplink default</td>
</tr>
<tr>
<td><strong>QSFP56 / QSFP-DD / OSFP</strong></td>
<td>200–800 Gb</td>
<td>spine and modern high-density uplinks</td>
</tr>
</tbody>
</table>
<p>The <strong>Q</strong> stands for <em>quad</em>: a QSFP carries four lanes in the space of one SFP. That is the whole trick — QSFP28 is 100 Gb because it is four lanes of 25 Gb, and QSFP+ is 40 Gb because it is four lanes of 10 Gb.</p>
<p>Which leads directly to the useful consequence: <strong>breakout cables.</strong> Because a QSFP28 is physically four 25 Gb lanes, you can buy a cable with one QSFP28 on one end and four SFP28s on the other, and split a single 100 Gb switch port into four 25 Gb server ports. Written <code>1x100G → 4x25G</code>, or <code>4x25G breakout</code>. This is extremely common and it is how you get high port counts out of a switch that only has QSFP cages. The same trick gives you <code>1x40G → 4x10G</code>.</p>
<p>The physical compatibility rule: <strong>an SFP-family module fits any SFP-family cage, and a QSFP-family module fits any QSFP-family cage — but a QSFP will not fit an SFP cage.</strong> They are different sizes. Speed negotiation within a family is a separate question and often does not happen automatically, so a 25 Gb module in a 10 Gb switch port may or may not work depending on the switch.</p>
<h2 id="copper-rj45-which-still-exists">Copper RJ45, which still exists</h2>
<p>Not everything is optics. <strong>10GBASE-T</strong> runs 10 Gb over ordinary RJ45 on <strong>Cat6A</strong> to 100 m, and it is the reason some servers still have familiar-looking Ethernet ports.</p>
<p>It has real drawbacks in a datacenter: noticeably higher power per port, higher latency than DAC, and more heat. Which is why it lost the server-to-switch fight to SFP28 DAC. But it is still everywhere for exactly two jobs, and both matter to you:</p>
<ul>
<li><strong>Out-of-band management.</strong> BMC ports are almost always 1 Gb RJ45. Part 4.</li>
<li><strong>Anything cheap or old</strong> — small switches, appliances, PDUs, console servers, environmental sensors.</li>
</ul>
<p>Cable categories in the order you will meet them: <strong>Cat5e</strong> (1 Gb), <strong>Cat6</strong> (10 Gb to 55 m), <strong>Cat6A</strong> (10 Gb to 100 m). In a cabinet, use Cat6A and stop thinking about it.</p>
<h2 id="fibre-the-two-families">Fibre: the two families</h2>
<p>If a link uses transceivers and separate fibre, the fibre is one of two fundamentally different things and they are not interchangeable.</p>
<p><strong>Multimode (MMF)</strong> has a wide core — 50 microns — that lets light take multiple paths. Cheap optics, short distances. Datacenter grades are <strong>OM3</strong>, <strong>OM4</strong> and <strong>OM5</strong>. Jacket colour is <strong>aqua</strong> for OM3 and OM4, <strong>lime green</strong> for OM5.</p>
<p><strong>Singlemode (SMF)</strong> has a 9-micron core so light takes essentially one path. Expensive optics, enormous distances. The datacenter grade is <strong>OS2</strong>, and the jacket is <strong>yellow</strong>.</p>
<p><strong>The colour of the jacket genuinely tells you the type</strong>, and this is one of the few places in the building where a visual convention is reliable enough to act on. Aqua is multimode. Yellow is singlemode. If someone sends you a photo of a patch panel, you can read the fibre type off it.</p>
<p>Reach, which is the whole reason the distinction exists:</p>
<table>
<thead>
<tr>
<th>Link</th>
<th>OM3</th>
<th>OM4</th>
<th>OS2 (singlemode)</th>
</tr>
</thead>
<tbody>
<tr>
<td>10 Gb (10GBASE-SR / -LR)</td>
<td>~300 m</td>
<td>~400 m</td>
<td>10 km</td>
</tr>
<tr>
<td>40 Gb (40GBASE-SR4)</td>
<td>~100 m</td>
<td>~150 m</td>
<td>10 km</td>
</tr>
<tr>
<td>100 Gb (100GBASE-SR4)</td>
<td>~70 m</td>
<td>~100 m</td>
<td>10 km</td>
</tr>
</tbody>
</table>
<p>Two things to take from that table. <strong>Multimode reach collapses as speed rises</strong> — the same OM4 fibre that carried 10 Gb for 400 m carries 100 Gb for 100 m. And <strong>singlemode does not care</strong>, which is why long runs, cross-connects and anything leaving the building are singlemode.</p>
<p>The transceiver naming follows: <strong>SR</strong> is short reach (multimode), <strong>LR</strong> is long reach (singlemode, 10 km), <strong>ER</strong> and <strong>ZR</strong> go further still. The trailing digit in <strong>SR4</strong> means four lanes, which means it needs eight fibres rather than two.</p>
<h2 id="fibre-connectors">Fibre connectors</h2>
<p><strong>LC</strong> is the small duplex connector — two fibres, one for transmit and one for receive, clipped together. It is what an SR or LR transceiver takes and it is the overwhelming majority of what you will see.</p>
<p><strong>MPO</strong> (also branded <strong>MTP</strong>) is a rectangular multi-fibre connector carrying 12 or 24 fibres in one ferrule. It exists for the parallel optics — a 40GBASE-SR4 or 100GBASE-SR4 needs eight fibres, so it takes an MPO rather than an LC. MPO is also how high-density trunk cabling gets run between patch panels: one trunk, then a cassette that fans it out to LC.</p>
<p><strong>SC</strong> is the older, larger square connector. Still found on carrier hand-offs and in the MMR.</p>
<p>Two things about fibre worth knowing even though you will never handle it. <strong>Polarity</strong> — transmit must land on receive at the far end, and with MPO trunks there are three standardised polarity methods (A, B and C) that a cabling team has to keep consistent; getting it wrong gives you a dark link with no obvious cause. And <strong>cleanliness</strong> — a fingerprint on a ferrule end-face is enough to kill a 100 Gb link, so fibre gets cleaned before every insertion and capped when unplugged. If a new fibre link is dark and everything on paper is right, "please clean and reseat both ends" is a legitimate and very common first remote hands request.</p>
<h2 id="transceiver-coding-the-annoying-part">Transceiver coding, the annoying part</h2>
<p>Transceivers carry a small EEPROM identifying the vendor. Some switches — Cisco historically being the notable one — refuse modules that do not identify as their own. Third-party vendors sell the same optics <strong>coded</strong> for a given switch brand, at a fraction of the price.</p>
<p>This is worth knowing for two reasons. When you order optics you must specify the switch they are going into, not just the speed. And if a link is dead and the switch log says something about an unsupported or unrecognised transceiver, that is a coding problem, not a hardware fault, and no amount of reseating will fix it.</p>
<p>On the good side, transceivers support <strong>DOM/DDM</strong> — Digital Optical Monitoring — which exposes real-time transmit power, receive power and temperature per module. That is how you tell a dirty connector from a dead laser from a fibre someone stood on, without going near the building. Your switch will show it. If you are not scraping optical Rx power into your metrics, you are missing the single best early warning of a degrading link.</p>
<h2 id="choosing-in-one-flowchart">Choosing, in one flowchart</h2>
<pre data-lang-none><code>   Both ends in the SAME cabinet?
        │
        ├── yes ──► DAC. Passive if ≤3 m, active up to ~10 m.
        │           Cheapest, coolest, lowest latency. Measure the real path.
        │
        └── no ──►  Does it pass through a patch panel?
                        │
                        ├── yes ──► Transceivers + fibre. Always.
                        │           (An AOC cannot be patched — the ends are moulded on.)
                        │
                        └── no ──►  Under ~100 m, point to point?
                                        │
                                        ├── yes ──► AOC is simple and cheap enough.
                                        │
                                        └── no ──►  Transceivers + singlemode (OS2, LR).
</code></pre>
<h2 id="cabling-hygiene-that-is-actually-your-problem">Cabling hygiene that is actually your problem</h2>
<p>Most of this is done by people with more skill than me, but three of them are decisions you make, not them.</p>
<p><strong>Order the right length, not the nearest length.</strong> Slack has to live somewhere, and in a rear cabinet channel there is nowhere for it to live. Cut-to-length or correctly-chosen patch leads are the difference between a cabinet you can work in and one where every change risks disturbing something else.</p>
<p><strong>Leave a service loop on anything on sliding rails.</strong> Enough slack to pull the machine fully forward. Covered in Part 1; it belongs here too, because it is a cabling decision.</p>
<p><strong>Label both ends, with the far end's identity on the label.</strong> <code>→ sw-c07-01 Et1/14</code> on the server end, <code>→ srv-web-11 eno1</code> on the switch end. No tracing, ever.</p>
<p>And one convention that costs nothing: <strong>use cable colour as a type signal.</strong> Whatever scheme you like — say, one colour for production data, one for out-of-band management, one for cross-connects — as long as it is written down and consistent. It makes a photograph from remote hands into something you can actually diagnose from.</p>
<h2 id="the-one-line-summary">The one-line summary</h2>
<p>Same cabinet, <strong>DAC</strong>. Adjacent cabinets, <strong>AOC</strong>. Through a panel or across the room, <strong>transceivers and fibre</strong> — aqua multimode for short, yellow singlemode for long. <strong>Q means four lanes</strong>, which is why 100 Gb breaks out into four 25 Gb. Order by exact length and exact switch model, and label both ends with the far end.</p>
<p>Next up: <strong>Part 4 — out-of-band access</strong>, which is the difference between fixing a machine from your laptop and filing a ticket.</p>]]></content:encoded>
  </item>
  <item>
    <title>Data Center Part 2: Power, A-side and B-side</title>
    <link>https://justmeandlinux.com/dc-02-power-a-side-b-side/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/dc-02-power-a-side-b-side/</guid>
    <pubDate>Fri, 21 Aug 2026 09:00:00 GMT</pubDate>
    <category>Linux</category>
    <description>You are sold a 30 amp feed. You can use 24 of them. If you also want redundancy, you can use 12. Power is the part of the datacenter that punishes vagueness hardest, because the failure mode is not a warning in a dashboard — it is a breaker opening and half a…</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<p>You are sold a 30 amp feed. You can use 24 of them. If you also want redundancy, you can use 12.</p>
<p>Power is the part of the datacenter that punishes vagueness hardest, because the failure mode is not a warning in a dashboard — it is a breaker opening and half a cabinet going dark instantly. This part is the arithmetic, the connectors, and the two mistakes that account for most cabinet-level outages.</p>
<h2 id="a-side-and-b-side-properly">A-side and B-side, properly</h2>
<p>Your cabinet receives two feeds. They are called <strong>A</strong> and <strong>B</strong>, and the entire point is that they share nothing upstream: different breakers, different distribution panels, ideally different UPS systems and different generator paths.</p>
<pre data-lang-none><code>   utility ──┬── UPS-A ── panel A ── breaker A ── whip A ──┐
             │                                              │
             │                                       ┌──────v──────┐
   generator ┤                                       │   PDU-A     │
             │                                       │  (0U, left) │
             │                                       └──────┬──────┘
             │                                              │
             │                                     ┌────────v────────┐
             │                                     │  server         │
             │                                     │  PSU1 ─── A     │
             │                                     │  PSU2 ─── B     │
             │                                     └────────^────────┘
             │                                              │
             │                                       ┌──────┴──────┐
             │                                       │   PDU-B     │
             │                                       │ (0U, right) │
             │                                       └──────^──────┘
             │                                              │
             └── UPS-B ── panel B ── breaker B ── whip B ───┘
</code></pre>
<p>The <strong>whip</strong> is the flexible armoured cable that drops from the overhead busway or comes up through the floor and terminates in a socket at your cabinet. The <strong>PDU</strong> plugs into it.</p>
<p>Colour convention is near-universal and worth trusting: <strong>A is usually blue, B is usually red</strong>, on the PDU bodies, the whips and often the power cords themselves. It is not a standard, but every facility I have seen documentation for does something like it, and it makes "both cords are in the same side" visible from three metres away instead of invisible forever.</p>
<h2 id="the-80-rule-which-is-where-the-capacity-goes">The 80 % rule, which is where the capacity goes</h2>
<p>In the US, the National Electrical Code treats anything drawing current for three hours or more as a <strong>continuous load</strong>. Every server is a continuous load. A continuous load may not exceed <strong>80 % of the breaker's rating</strong>.</p>
<p>So a 30 A circuit gives you 24 A of usable continuous draw. Not 30. The last 6 A exists so the breaker does not sit permanently on the edge of tripping from thermal drift.</p>
<p>This is a US code framing — elsewhere the equivalent constraint comes through IEC practice, the PDU's own rating and the facility's contract rather than the NEC — but <strong>the practical outcome is the same everywhere: you do not get to use the number on the label.</strong> Ask your facility what they will actually let you draw, and get it in writing. Many PDUs will alarm, and some facilities will bill or shut you off, well before the breaker itself opens.</p>
<p>In kilowatts, for the feeds you are most likely to meet:</p>
<table>
<thead>
<tr>
<th>Feed</th>
<th>Nameplate</th>
<th>Usable at 80 %</th>
</tr>
</thead>
<tbody>
<tr>
<td>120 V, 20 A, single phase</td>
<td>2.4 kW</td>
<td>1.92 kW</td>
</tr>
<tr>
<td>208 V, 20 A, single phase</td>
<td>4.16 kW</td>
<td>3.33 kW</td>
</tr>
<tr>
<td>208 V, 30 A, single phase</td>
<td>6.24 kW</td>
<td>4.99 kW</td>
</tr>
<tr>
<td>208 V, 30 A, three phase</td>
<td>10.8 kW</td>
<td>8.65 kW</td>
</tr>
</tbody>
</table>
<p>Three-phase looks like magic until you see the formula: <code>kW = 1.732 × volts × amps</code>. The 1.732 is the square root of three. It is the reason a three-phase 30 A feed carries roughly 73 % more than a single-phase 30 A feed on the same amperage number, and it is why dense cabinets are three-phase.</p>
<p><strong>208 V rather than 120 V is the norm in datacenters</strong> and is slightly better than 120 V for the same equipment, because server PSUs run marginally more efficiently at the higher voltage and you get more watts per amp of breaker.</p>
<h2 id="redundancy-costs-you-half">Redundancy costs you half</h2>
<p>Here is the part that surprised me most, and it is the single most useful thing in this post.</p>
<p>If the point of A and B is that either one can carry the cabinet alone, then <strong>the total cabinet load must fit inside one feed's 80 % limit</strong> — not inside the sum of both.</p>
<p>Two 30 A feeds do not give you 48 A of usable capacity. They give you 24 A of usable capacity, delivered over two paths.</p>
<p>In normal operation the load splits roughly evenly, so each feed carries about 12 A — <strong>40 % of its rating.</strong> That looks like waste right up until the moment one side drops.</p>
<p>The thing that goes wrong when people ignore this has a name: <strong>swing load</strong>. Both feeds are running at, say, 70 %. The A-side breaker opens. Every dual-corded server instantly moves its entire draw to the B side, which is now asked for 140 % of its rating. The B breaker opens too. You have just converted a single-feed failure into a total cabinet outage, and you did it by using capacity you were sold but could not actually have.</p>
<pre data-lang-none><code>   Designed correctly (2N):          Designed wrong:
   A: 40%  ████                      A: 70%  ███████
   B: 40%  ████                      B: 70%  ███████

   A fails ↓                          A fails ↓

   A:  0%                             A:  0%
   B: 80%  ████████  holds            B: 140% ██████████████  TRIPS
                                          → whole cabinet down
</code></pre>
<p>If you take one number from this series, take this one: <strong>in a 2N cabinet, target 40 % per feed and treat 40 % as full.</strong></p>
<p>There is a softer variant. If your fleet is genuinely tolerant of losing a fraction of nodes — stateless web tier, three-way replicated storage — some operators deliberately run <strong>primary/primary</strong>, loading both feeds to 80 % and accepting that a feed failure kills half the cabinet. That is a legitimate engineering choice and it doubles your usable power. It is only legitimate if it is a <em>decision</em>, written down, with the blast radius understood. Drifting into it by accident is the failure mode described above.</p>
<h2 id="pdu-types-and-which-one-you-want">PDU types, and which one you want</h2>
<p>PDUs come in four escalating tiers, and the difference is entirely about what you can see and do remotely.</p>
<p><strong>Basic.</strong> A metal strip with outlets. No intelligence, no network port, no measurement. Cheap. You have no idea what you are drawing.</p>
<p><strong>Metered.</strong> Reports total current draw for the PDU, either on a local display or over the network. This is the minimum I would accept, because without it the 40 % rule above is unenforceable — you cannot manage a number you cannot see.</p>
<p><strong>Monitored.</strong> Per-outlet measurement. Now you can tell which server is drawing what, spot a PSU that has quietly failed over, and do capacity planning that is not guesswork.</p>
<p><strong>Switched.</strong> Per-outlet relays, so you can power-cycle an individual outlet over the network. This is the one that matters operationally, for a reason covered properly in Part 4: a switched PDU is the only remote reset that works when the BMC itself is wedged. It is the difference between "I can fix this from my laptop at 3 a.m." and "I need to open a remote hands ticket and wait."</p>
<p>Modern units combine tiers — "switched and monitored" is common — and most speak SNMP, plus increasingly a REST API. Get them into Prometheus. Cabinet power draw is a genuinely useful metric and almost nobody graphs it.</p>
<h2 id="the-connectors">The connectors</h2>
<p>This is the vocabulary you asked about, and it is smaller than it looks. The <strong>IEC 60320</strong> family covers the cords between the PDU and your equipment, and there are really only four pieces.</p>
<table>
<thead>
<tr>
<th>Connector</th>
<th>What it is</th>
<th>Rating</th>
<th>Where you see it</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>C13</strong></td>
<td>socket (female)</td>
<td>10 A</td>
<td>on the PDU; the end that plugs into a normal server</td>
</tr>
<tr>
<td><strong>C14</strong></td>
<td>inlet (male)</td>
<td>10 A</td>
<td>on the back of the server PSU</td>
</tr>
<tr>
<td><strong>C19</strong></td>
<td>socket (female)</td>
<td>16 A</td>
<td>on the PDU, for high-draw kit</td>
</tr>
<tr>
<td><strong>C20</strong></td>
<td>inlet (male)</td>
<td>16 A</td>
<td>on big PSUs, blade chassis, large switches</td>
</tr>
</tbody>
</table>
<p>The pairing rule is trivial once you see it: <strong>odd numbers are sockets, the next even number up is the matching inlet.</strong> A C13-to-C14 cord runs from the PDU to an ordinary 1U server. A C19-to-C20 cord runs from the PDU to something hungry.</p>
<p>Two practical notes. First, <strong>locking variants exist</strong> — C13/C14 with a latch, or the C15/C16 heat-rated version — and they are worth specifying, because the most embarrassing outage in this entire domain is a power cord walking out of a socket a few millimetres while someone works in the cabinet next to it. Many datacenter PDUs have a built-in retention clip. Use it. Second, <strong>C15 is not C13 with a different name</strong>: it is rated for higher temperatures and has a notch, and a C15 plug will not seat in a C13 socket.</p>
<p>The other family is <strong>NEMA</strong>, which is the plug on the <em>input</em> side of the PDU, going into the facility's whip. You will see:</p>
<ul>
<li><strong>L6-30P</strong> — the twist-lock plug for a 208 V, 30 A single-phase feed. Extremely common.</li>
<li><strong>L6-20P</strong> — same but 20 A.</li>
<li><strong>L21-30P</strong> — five-wire twist-lock for 208 V three-phase wye, 30 A.</li>
<li><strong>L15-30P</strong> — four-wire, three-phase delta.</li>
</ul>
<p>The <strong>L</strong> means locking: you push and twist, and it cannot be pulled out. You will never handle one of these. You will absolutely have to <em>quote</em> one when ordering a PDU, because a PDU with the wrong input plug is a paperweight — and the whip is fixed infrastructure that the facility installed, so the PDU is what has to match.</p>
<h2 id="what-to-actually-do-with-all-this">What to actually do with all this</h2>
<p>Four things, in order of how much trouble they save.</p>
<p><strong>Know your budget in kilowatts, not amps.</strong> Vendors quote server power in watts. Facilities quote feeds in amps. Convert everything to kW once, at 80 %, and keep it in the same document as the rack elevation.</p>
<p><strong>Add up nameplate, then measure.</strong> Nameplate PSU ratings are worst case and are usually far above real draw — a 750 W PSU in a 1U web server might pull 150 W idle and 300 W under load. Plan on nameplate so you never trip a breaker; measure real draw with a metered PDU so you know how much headroom you truly have. Those two numbers can differ by a factor of three, and the gap is where your remaining capacity lives.</p>
<p><strong>Audit the cording.</strong> For every dual-PSU machine, confirm one cord is in A and one in B. This is a perfect remote hands job and it is worth doing once as an explicit ticket, with photos, rather than assuming. Ask for a photo of each PDU's outlet strip with the labels visible.</p>
<p><strong>Know which machines are single-corded.</strong> Some things — cheap switches, older appliances, small NAS units — have one PSU. In a 2N cabinet these are your true single points of failure, and the mitigation is either a <strong>transfer switch</strong> (a small unit with two inputs and one output that switches automatically) or accepting the risk deliberately. Either is fine. Not knowing which machines they are is not.</p>
<h2 id="the-one-line-summary">The one-line summary</h2>
<p>Two feeds, sized so either can carry the whole cabinet: <strong>40 % each is full.</strong> C13/C14 for normal kit, C19/C20 for hungry kit, odd is socket and even is inlet. Get switched-and-monitored PDUs so you can see draw per outlet and power-cycle without a ticket.</p>
<p>Next up: <strong>Part 3 — cables, optics and transceivers</strong>, which is where DAC, AOC, SFP28 and QSFP28 stop being noise and start being a decision you can make.</p>]]></content:encoded>
  </item>
  <item>
    <title>Data Center Part 1: Anatomy of a rack</title>
    <link>https://justmeandlinux.com/dc-01-anatomy-of-a-rack/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/dc-01-anatomy-of-a-rack/</guid>
    <pubDate>Fri, 21 Aug 2026 08:00:00 GMT</pubDate>
    <category>Linux</category>
    <description>A 42U cabinet does not give you 42U of usable space, and the sooner you internalise that the fewer awkward conversations you will have about why the new server does not fit. This part is the physical frame: what the numbers mean, what holds equipment in, why…</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<p>A 42U cabinet does not give you 42U of usable space, and the sooner you internalise that the fewer awkward conversations you will have about why the new server does not fit.</p>
<p>This part is the physical frame: what the numbers mean, what holds equipment in, why depth and weight matter more than height, and how to read the diagram that describes it all.</p>
<h2 id="nineteen-inches-and-why-everything-is-that-wide">Nineteen inches, and why everything is that wide</h2>
<p>The standard is <strong>EIA-310</strong>. It fixes the width of the equipment's mounting face at <strong>19 inches (482.6 mm)</strong> and the vertical pitch at <strong>1.75 inches (44.45 mm)</strong>, which is one <strong>U</strong>.</p>
<p>Two things follow that are worth saying out loud, because they trip people up.</p>
<p><strong>19 inches is the front panel width, not the cabinet width.</strong> The mounting rails are 19 inches apart at the face; the cabinet around them is wider. Colocation cabinets are typically <strong>600 mm</strong> or <strong>800 mm</strong> wide externally. That extra 200 mm on an 800 mm cabinet is not wasted — it is the vertical cable management channel down each side, and it is the difference between a rear that you can work in and a rear that is a rat's nest.</p>
<p><strong>U is a pitch, not a box.</strong> Equipment is built slightly under its nominal U height so it can slide in without binding. A 1U server is about 44 mm tall, not exactly 44.45. This is why you cannot stack equipment to fill an arbitrary gap: mounting holes only exist at U boundaries.</p>
<pre data-lang-none><code>     ← ─────────  19" / 482.6 mm mounting face  ───────── →
    ┌─┬──────────────────────────────────────────────────┬─┐
    │o│                                                  │o│  ┐
    │o│                 1U of equipment                  │o│  │ 44.45 mm
    │o│                                                  │o│  ┘
    ├─┼──────────────────────────────────────────────────┼─┤
    │o│                                                  │o│
    │o│                 1U of equipment                  │o│
    │o│                                                  │o│
    └─┴──────────────────────────────────────────────────┴─┘
      ^                                                  ^
      └── mounting rail, punched with holes in            ┘
          repeating groups of three per U
</code></pre>
<p>The holes come in <strong>groups of three per U</strong>, unevenly spaced — a wide gap, then two closer together. That uneven pattern is how you find a U boundary by eye. If a technician mounts something one hole out of alignment, everything above it in the rack is now offset by a third of a U and nothing else will line up. This is a real and common failure, and it is why "start from a marked U" is standard practice.</p>
<h2 id="square-holes-cage-nuts-and-threaded-holes">Square holes, cage nuts, and threaded holes</h2>
<p>Three kinds of rail exist and you should know which you have.</p>
<p><strong>Square holes</strong> are the modern datacenter default. The hole is a plain square with no thread. You clip a <strong>cage nut</strong> into it — a small square nut in a springy sheet-metal cage — and then screw into that. The advantage is flexibility: any thread size, any equipment, replaceable if you strip one.</p>
<p><strong>Round threaded holes</strong> (usually M6 or 10-32) are older and common in telecom and small office racks. You screw directly into the rail. Simpler, but a stripped thread damages the rack rather than a 20-cent part.</p>
<p><strong>Tool-less rails</strong> clip straight into square holes with no screws at all. Most modern 1U and 2U servers ship with these, and they are why racking a server is now a two-minute job rather than a twenty-minute one.</p>
<p>Cage nuts have a correct orientation — they insert from the <em>rear</em> of the rail with the spring clips facing the installer — and they are the single most-cursed component in the building because installing them by hand hurts. There is a dedicated tool. Technicians have one. This is not your problem, but if a ticket comes back saying a cage nut was missing at a given position, now you know what was being asked for.</p>
<h2 id="rails-and-the-depth-problem">Rails, and the depth problem</h2>
<p>A server does not sit on a shelf. It mounts on a pair of <strong>rails</strong> that bolt to the front <em>and rear</em> posts, and the server slides onto them.</p>
<p>This means the distance between the front and rear posts must match what the rail kit supports. Rail kits have an adjustment range — commonly something like 610–915 mm — and if your cabinet's post-to-post depth falls outside it, the rails do not fit. Full stop. No amount of effort at the far end fixes it.</p>
<p>Cabinet depths you will see are <strong>600, 800, 1000 and 1200 mm</strong> externally, with most datacenter cabinets in the 800–1070 mm range. A 600 mm deep cabinet is a <em>network</em> cabinet — switches and patch panels are rarely deeper than about 400 mm — and it will not take a modern server at all.</p>
<p><strong>Depth is the dimension that actually constrains you, not height.</strong> A GPU server or a high-density storage chassis can be long, and once you add the rear cable bend radius and a cable management arm you can run out of cabinet before you run out of U. When you spec a machine for a cage you did not design, the chassis depth and the rail kit's supported range are the two numbers to check first.</p>
<p><strong>Two kinds of rail behave differently.</strong> <em>Sliding</em> rails let the server pull forward for service without unracking it, which is what you want for anything with disks you will swap or lids you will open. <em>Static</em> rails are fixed; servicing means unbolting the machine and taking it out, with a second person if it is heavy. Sliding rails usually need a <strong>cable management arm</strong> and a <strong>service loop</strong> — deliberate slack in every rear cable so the machine can travel without unplugging itself. If a machine is racked on sliders with cables cut to exact length, it can only be serviced by disconnecting it, which quietly defeats the point.</p>
<h2 id="weight-and-why-the-bottom-of-the-rack-is-not-free-real-estate">Weight, and why the bottom of the rack is not free real estate</h2>
<p>Cabinets have a static load rating, often in the region of 1000–1500 kg, and floors have their own limit, which in a raised-floor room can be the tighter of the two.</p>
<p>The operational rule is simple and universal: <strong>heavy things go at the bottom.</strong> A full 4U disk shelf mounted at the top of a cabinet makes it top-heavy, and a top-heavy cabinet on castors is dangerous to move and can tip when a sliding-rail server is pulled fully out of it. Technicians know this. It is worth knowing too, so that when you plan an elevation you put the storage low and the 1U compute high, rather than the other way around.</p>
<h2 id="blanking-panels-are-not-cosmetic">Blanking panels are not cosmetic</h2>
<p>An empty U in the middle of a populated cabinet is a hole between the cold aisle and the hot aisle. Hot exhaust air flows back through it to the front of the rack, gets sucked into the next server's intake, and that server now runs hot for no reason a monitoring dashboard will ever explain.</p>
<p><strong>Blanking panels</strong> are blank sheets of metal or plastic that fill unused U positions and stop that recirculation. They cost very little and they are the single highest-return thing in a cabinet.</p>
<p>Two related airflow items in the same family: <strong>brush strips or grommets</strong> where cables pass through the cabinet floor or roof, and <strong>side panels</strong> fitted so air cannot bypass between adjacent cabinets. All three do the same job — force air to go through the equipment rather than around it.</p>
<p>If you are ever asked to approve a rack build and you see empty unblanked U positions in the elevation, that is a legitimate thing to comment on.</p>
<h2 id="0u-and-the-vertical-space">0U and the vertical space</h2>
<p>You will see PDUs described as <strong>0U</strong>. That means they mount vertically in the cabinet's side channel rather than horizontally in the 19-inch mounting space, so they consume no U at all. A 42U cabinet with two 0U PDUs still has 42U free.</p>
<p>That side channel is also where vertical cable managers live. It is the reason to pay for an 800 mm wide cabinet rather than a 600 mm one when the cabinet holds servers.</p>
<h2 id="reading-a-rack-elevation">Reading a rack elevation</h2>
<p>A <strong>rack elevation</strong> is the map of what is mounted where. It is what you send when you ask for something to be installed, and what you consult when you ask for something to be touched. It looks like this:</p>
<pre data-lang-none><code>  CABINET  DC1 / ROW-C / RACK-07                 (front view)
  ┌────┬─────────────────────────────────────────────────────┐
  │ 42 │  ░░░ blanking ░░░                                   │
  │ 41 │  patch panel  PP-01   (24 x LC duplex, to MMR)      │
  │ 40 │  switch  sw-c07-01    Arista 7050SX  48x10G+4x40G   │
  │ 39 │  switch  sw-c07-02    Arista 7050SX  48x10G+4x40G   │
  │ 38 │  ░░░ blanking ░░░                                   │
  │ 37 │  srv-web-11    Dell R650   1U                       │
  │ 36 │  srv-web-12    Dell R650   1U                       │
  │ 35 │  srv-web-13    Dell R650   1U                       │
  │ .. │  ...                                                │
  │ 04 │  ┌───────────────────────────────────────────┐      │
  │ 03 │  │  srv-stor-01   Supermicro 4U  36-bay      │      │
  │ 02 │  │  (heavy — bottom of rack)                 │      │
  │ 01 │  └───────────────────────────────────────────┘      │
  └────┴─────────────────────────────────────────────────────┘
     0U left channel:  PDU-A   0U right channel:  PDU-B
</code></pre>
<p>Some conventions to note.</p>
<p><strong>A U position is a location, and it is how you name things to remote hands.</strong> "The server at U37" is unambiguous in a way that "the third one down" never is.</p>
<p><strong>Which end is U1 is not universally standardised.</strong> The common convention numbers from the <em>bottom</em> up, so U1 is the lowest position — and most cabinets are physically labelled that way on the rail. But it is not a guarantee: some manufacturers and some organisations number from the top down, and NetBox has carried a long-running discussion about supporting both because real deployments do both. So do not assume. <strong>Look at how your own cabinet's rails are printed, and match that.</strong> If a cabinet is unlabelled, agree a convention with the facility in writing before the first install, and put it in the elevation.</p>
<p><strong>A multi-U device occupies a range and is referenced by its lowest U.</strong> The 4U storage box above lives at U1–U4 and you call it "U1".</p>
<p><strong>Front and rear are separate views.</strong> Some things — PDUs, rear-mounted patch panels, cable managers — appear only in the rear elevation. If your documentation only has a front view, half the cabinet is undocumented, and that half is the half with all the cables in it.</p>
<p>If you keep this in NetBox or an equivalent DCIM, keep it <em>accurate</em> rather than complete. An elevation that is 60 % filled in but true is worth more than one that is 100 % filled in and eight months stale, because remote hands will act on it.</p>
<h2 id="labelling">Labelling</h2>
<p>The standard here is <strong>ANSI/TIA-606</strong> (currently at revision D), which sets out how to identify cabling infrastructure, and it dovetails with <strong>TIA-942</strong>, the datacenter standard that divides a facility into functional areas — entrance room, main distribution area, horizontal distribution area, equipment distribution area, and so on.</p>
<p>You almost certainly do not need to implement TIA-606 formally. What you need is the idea behind it: <strong>a hierarchical identifier that reads from the outside in.</strong> Room, row, cabinet, panel, port. Something like <code>DC1-C07-PP01-14</code> tells anyone in the building exactly where to stand and what to touch, and it sorts sensibly in a spreadsheet.</p>
<p>The two rules that pay for themselves regardless of standards: <strong>label both ends of every cable</strong>, and <strong>make the label say where the other end is</strong>. A cable tail that reads <code>→ sw-c07-01 Gi1/0/14</code> means nobody ever has to trace it by hand.</p>
<h2 id="the-five-numbers-to-know-about-your-own-cabinet">The five numbers to know about your own cabinet</h2>
<p>If you take one thing from this part, make it this list. These are the facts about your cage that you should be able to answer without looking:</p>
<ol>
<li><strong>Cabinet identifier</strong> — room, row, position, in whatever scheme the facility uses.</li>
<li><strong>Height in U, and how many are free</strong> — plus whether U1 is at the top or the bottom.</li>
<li><strong>Usable depth</strong>, post to post, so you can check a rail kit before you buy a server.</li>
<li><strong>Power</strong> — how many feeds, at what voltage and amperage. Part 2.</li>
<li><strong>Uplinks</strong> — what the cabinet's switches connect to, and how many ports are spare. Part 5.</li>
</ol>
<p>I could not answer three of those five for my own kit when I started writing this. That is exactly the gap this series exists to close.</p>
<p>Next up: <strong>Part 2 — power, A-side and B-side</strong>, where the 80 % rule turns a 30 A feed into rather less than 30 A.</p>]]></content:encoded>
  </item>
  <item>
    <title>Data Center Part 0: The vocabulary</title>
    <link>https://justmeandlinux.com/dc-00-vocabulary/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/dc-00-vocabulary/</guid>
    <pubDate>Fri, 21 Aug 2026 07:00:00 GMT</pubDate>
    <category>Linux</category>
    <description>I have never racked a server. I have opened a lot of tickets asking someone else to. That is the normal shape of this job now. You are a competent Linux engineer, you own machines that live in a building you have visited maybe twice, and every physical act on…</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<p>I have never racked a server. I have opened a lot of tickets asking someone else to.</p>
<p>That is the normal shape of this job now. You are a competent Linux engineer, you own machines that live in a building you have visited maybe twice, and every physical act on those machines happens through a stranger's hands and a ticketing system. It works fine right up until the moment someone says "the B-side feed is down on the cabinet, can you confirm your dual-corded gear is holding" and you realise you are nodding at words rather than understanding them.</p>
<p>This series is me closing that gap, written down. It is not a datacenter design course — I am not going to teach you to size a CRAC unit or argue about Tier ratings, because neither of us is ever going to do that. It is the subset a Linux engineer needs in order to describe their own hardware accurately, read a facility's paperwork, and write a remote hands ticket that gets the job done on the first attempt.</p>
<p>Part 0 is vocabulary. If you already know what a demarc is, skip to Part 1.</p>
<h2 id="the-five-nested-boxes">The five nested boxes</h2>
<p>Almost every term in this series is a location. The whole model is five boxes inside each other, and knowing which box a word belongs to is most of the battle.</p>
<pre data-lang-none><code>┌─ FACILITY ────────────────────────────────────────────────┐
│  the building. Has an address, a loading dock, a security  │
│  desk, generators, chillers, and a Meet-Me Room.           │
│                                                            │
│  ┌─ DATA HALL ─────────────────────────────────────────┐  │
│  │  one room full of rows. Also called a "suite".      │  │
│  │                                                      │  │
│  │  ┌─ CAGE ────────────────────────────────────────┐  │  │
│  │  │  your fenced-off floor space. Locked. Yours.  │  │  │
│  │  │  (If you rent single cabinets instead, you    │  │  │
│  │  │   have no cage — skip this box.)              │  │  │
│  │  │                                                │  │  │
│  │  │  ┌─ RACK / CABINET ────────────────────────┐  │  │  │
│  │  │  │  the 19-inch steel frame. 42U-48U tall. │  │  │  │
│  │  │  │                                          │  │  │  │
│  │  │  │  ┌─ SERVER ──────────────────────────┐  │  │  │  │
│  │  │  │  │  the thing you actually ssh into. │  │  │  │  │
│  │  │  │  └───────────────────────────────────┘  │  │  │  │
│  │  │  └──────────────────────────────────────────┘  │  │  │
│  │  └────────────────────────────────────────────────┘  │  │
│  └──────────────────────────────────────────────────────┘  │
└────────────────────────────────────────────────────────────┘
</code></pre>
<p>Your ssh session reaches the innermost box. Everything else in this series is one of the outer four, and every one of them can take your service down without a single thing being wrong inside Linux.</p>
<h2 id="rack-and-cabinet">Rack and cabinet</h2>
<p>Used interchangeably in conversation. Strictly, a <strong>rack</strong> is an open frame — four posts, no sides — and a <strong>cabinet</strong> is an enclosed rack with doors and side panels. Colocation is almost always cabinets, because the doors are lockable and the enclosure is what makes front-to-back airflow work.</p>
<p>The number you will see everywhere is <strong>U</strong> (or <strong>RU</strong>, rack unit): the vertical unit of mounting space, standardised at 1.75 inches / 44.45 mm. A "1U server" is one unit tall, a "2U server" is two. A typical colo cabinet is 42U or 48U. Part 1 pulls this apart properly.</p>
<h2 id="front-and-back">Front and back</h2>
<p>This is the one that surprised me by how much it matters, and it is the answer to "what do I expect at the front versus the back of the rack".</p>
<p><strong>The front is where the cold air is and where nothing is plugged in.</strong> Front of a server: bezel, drive bays, power button, status LEDs, a service tag, sometimes a small LCD or a USB/serial port for a technician. That is essentially it.</p>
<p><strong>The back is where every cable is.</strong> Network, power, out-of-band management, console. All of it.</p>
<pre data-lang-none><code>        COLD AISLE                              HOT AISLE
   (air conditioning in)                    (hot exhaust out)
            │                                       │
            v                                       v
    ┌───────────────────┐                   ┌───────────────────┐
    │  FRONT of rack    │                   │   REAR of rack    │
    │                   │  ═══ airflow ══&gt;  │                   │
    │  · drive bays     │                   │  · NIC ports      │
    │  · power button   │                   │  · BMC/IPMI port  │
    │  · status LEDs    │                   │  · PSU inlets     │
    │  · service tag    │                   │  · PDUs (A and B) │
    │  · blanking panels│                   │  · patch panels   │
    └───────────────────┘                   └───────────────────┘
</code></pre>
<p>Servers pull cold air in the front and blow hot air out the back. Rows are arranged so that all the fronts face each other across one aisle (the <strong>cold aisle</strong>) and all the backs face each other across the next (the <strong>hot aisle</strong>). That is <strong>hot aisle / cold aisle</strong> containment, and it is why you cannot simply turn a cabinet around to make cabling easier.</p>
<p>Practical consequence for you: <strong>almost every remote hands task happens at the back.</strong> If your ticket says "the port on the left" without saying whether you mean facing the front or the back, you have written an ambiguous ticket. Say <em>rear</em>, and say left-or-right <em>as viewed from the rear</em>.</p>
<h2 id="a-side-and-b-side">A-side and B-side</h2>
<p>Your cabinet gets two independent power feeds, traced back through separate breakers, separate distribution panels, and ideally separate UPS systems. They are called the <strong>A-side</strong> and the <strong>B-side</strong>. Each feeds its own <strong>PDU</strong> (power distribution unit — the metal strip of outlets running down the inside of the cabinet).</p>
<p>A server with two power supplies has one cord in the A PDU and one in the B PDU. Lose an entire side — a tripped breaker, a UPS in maintenance — and the server keeps running on the other.</p>
<p>Two things I did not appreciate until I went looking. First, this only protects you if the machine genuinely has two PSUs and both are genuinely plugged into different sides; a single-PSU box in a dual-fed cabinet has no redundancy at all, and it is depressingly easy to have one cabinet in which somebody cabled both cords of a server into the same PDU. Second, redundancy costs you capacity: if either side must be able to carry the whole cabinet alone, neither side can normally run above roughly half its usable rating. Part 2 does the arithmetic, including the 80 % rule that makes "usable" smaller than you think.</p>
<h2 id="demarc">Demarc</h2>
<p>The <strong>demarcation point</strong>, universally shortened to <strong>demarc</strong>, is the boundary of responsibility. On one side, the facility's or carrier's equipment and problem. On the other, yours.</p>
<p>Physically it is normally unglamorous: a patch panel, in or near your cage, where the facility's cabling ends and you are allowed to plug in. When a circuit breaks, the first question anyone asks is <em>which side of the demarc</em> the fault is on, because that decides whose job it is to fix.</p>
<p>The word comes from telephony, where the demarc was the box on the outside of the building. Same idea, indoor version.</p>
<h2 id="meet-me-room-and-cross-connect">Meet-Me Room and cross-connect</h2>
<p>The <strong>Meet-Me Room</strong> (<strong>MMR</strong>) is a secure room in the facility where all the carriers, cloud on-ramps and other tenants terminate their networks. Nobody gets casual access to it. It is the building's switchboard.</p>
<p>A <strong>cross-connect</strong> is a physical cable the facility installs for you between two points in the building — typically from your cage's demarc panel to some other party's termination in the MMR. It is not a service or a circuit. It is literally a piece of fibre or copper that a technician runs and patches, which is why it carries an install fee and a monthly rental and why it takes days rather than seconds.</p>
<p>To order one you supply an <strong>LOA-CFA</strong>: Letter of Authorization / Connecting Facility Assignment. The other party issues it, and it is the document that says <em>"this customer is permitted to connect to us, at this exact cabinet, panel and port."</em> Without it the facility will not patch anything, because they have no proof the far end consented. AWS, for example, issues an LOA-CFA for a Direct Connect port and it expires if the cross-connect is not completed in time.</p>
<p>Two more terms that ride along: the <strong>A-side</strong> and <strong>Z-side</strong> of a circuit. Confusingly this is <em>not</em> the A/B of power — here A is simply one end of the circuit and Z is the other. Context tells you which meaning is in play: power has A and B, circuits have A and Z.</p>
<h2 id="direct-connect">Direct connect</h2>
<p>A <strong>direct connection</strong> to a cloud provider is a cross-connect whose far end happens to be AWS, Azure or Google rather than a carrier. The commercial names are <strong>AWS Direct Connect</strong>, <strong>Azure ExpressRoute</strong> and <strong>Google Cloud Interconnect</strong>. Traffic to that cloud leaves your cabinet, crosses the MMR on your fibre, and lands on the provider's router without ever touching the public internet.</p>
<p>You will meet two flavours. A <strong>dedicated</strong> connection is a whole physical port that is yours, in AWS's case 1, 10, 100 or 400 Gbps, and you order the cross-connect yourself. A <strong>hosted</strong> connection is a slice of somebody else's port sold to you by a partner, available at much smaller sizes, with no cross-connect for you to order because the partner already has one. Part 6 covers which you actually want.</p>
<h2 id="dac-aoc-transceivers">DAC, AOC, transceivers</h2>
<p>A <strong>DAC</strong> is a Direct Attach Copper cable: a fixed length of twinaxial copper with the transceiver moulded onto both ends. You cannot unplug the ends. You buy it as one unit — "a 3-metre SFP28 DAC" — and it is the cheap, low-power, low-latency way to connect a server to a switch in the same rack. Cheap and reliable, but short: passive DACs are typically good to about 3 m and stretch to 5 or 7 m; active ones with signal conditioning built in reach roughly 10 m.</p>
<p>An <strong>AOC</strong> is an Active Optical Cable: same idea, ends moulded on, but the middle is fibre and the ends contain lasers. Much longer reach — up to about 100 m — for more money and more power.</p>
<p>A <strong>transceiver</strong> (a module, an "optic", an SFP) is the removable part on its own: you plug the transceiver into the switch and a separate fibre patch lead into the transceiver. That is what you use when the run is long, goes through a patch panel, or crosses to another rack.</p>
<p>The form factor names encode speed. <strong>SFP+</strong> is 10 Gb, <strong>SFP28</strong> is 25 Gb, <strong>QSFP+</strong> is 40 Gb, <strong>QSFP28</strong> is 100 Gb. Part 3 covers the whole family, plus which fibre goes with which and why the colour of the cable tells you something real.</p>
<h2 id="ipmi-bmc-idrac-ilo">IPMI, BMC, iDRAC, iLO</h2>
<p>Your server contains a second, tiny computer that runs whether or not the main machine is powered on. It is called the <strong>BMC</strong> — Baseboard Management Controller. It has its own processor, its own firmware, its own network stack, and its own IP address, and it can power the server on and off, show you the console, mount an ISO, and read every temperature and fan in the chassis.</p>
<p><strong>IPMI</strong> is the old standard protocol for talking to it. <strong>Redfish</strong> is the modern HTTPS/JSON replacement. The vendors each brand their BMC differently, which is why the same thing has four names: Dell calls it <strong>iDRAC</strong>, HPE calls it <strong>iLO</strong>, Lenovo <strong>XCC</strong>, Supermicro just <strong>IPMI</strong>.</p>
<p>The cable question you asked about: on the back of the server there is usually a small RJ45 labelled <em>BMC</em>, <em>IPMI</em>, <em>iDRAC</em> or <em>iLO</em>, physically separate from the data NICs. That is the <strong>dedicated</strong> management port and it is the one you want. The alternative is <strong>shared</strong> mode, where the BMC borrows one of the main NIC ports over a sideband channel called NC-SI. Shared saves you a switch port and a cable; it also means that whatever takes out the main NIC takes out your way of fixing the main NIC. Part 4 argues that one out properly, along with why a BMC must never be reachable from the internet.</p>
<h2 id="remote-hands-and-smart-hands">Remote hands and smart hands</h2>
<p><strong>Remote hands</strong> is the basic, usually contract-included tier: a technician follows precise instructions. Press this button. Reseat that cable. Read me the LEDs. Tell me what the LCD says. They are not expected to diagnose anything, and you should not expect them to.</p>
<p><strong>Smart hands</strong> is the chargeable tier for work that needs judgement or real hardware skill: racking and cabling a new server, replacing a PSU or a disk, receiving and unboxing a shipment, testing a circuit.</p>
<p>The distinction matters commercially, but the operational lesson is the same for both: <strong>the technician cannot see your monitoring, does not know your naming scheme, and is standing in a loud cold room in front of forty identical black boxes.</strong> Every ambiguity in your ticket becomes either a phone call or a mistake. Part 7 is entirely about writing tickets that survive that environment.</p>
<h2 id="what-is-coming">What is coming</h2>
<ul>
<li><strong>Part 1</strong> — the rack itself: U numbering, rails, cage nuts, depth, weight, blanking panels, and how to read a rack elevation.</li>
<li><strong>Part 2</strong> — power: A/B feeds, PDU types, C13 through C20, the 80 % rule, and why redundancy halves your capacity.</li>
<li><strong>Part 3</strong> — cables and optics: DAC, AOC, transceivers, multimode versus singlemode, LC and MPO, and picking a length.</li>
<li><strong>Part 4</strong> — out-of-band: BMC, IPMI, Redfish, serial consoles, switched PDUs, and the three different things "reboot it" can mean.</li>
<li><strong>Part 5</strong> — where the switches live: top-of-rack versus end-of-row, leaf-spine, uplinks, oversubscription.</li>
<li><strong>Part 6</strong> — leaving the cage: demarc, MMR, cross-connects, direct connects, transit, peering and IXs.</li>
<li><strong>Part 7</strong> — directing remote hands: the ticket template, the standard jobs, and how to verify the work.</li>
<li><strong>Part 8</strong> — the cheat sheet: every number and connector in this series on one page.</li>
</ul>
<p>A note on how this was written, since the blog's rule is not to claim experience I do not have: this series is built from vendor and standards documentation, facility operator guidance, and the parts of the job I do touch — the tickets, the paperwork and the Linux side of the machine. Where something is a genuine hardware-handling skill, I have said so and pointed at the people who have it, rather than pretending otherwise.</p>
<p>Next up: <strong>Part 1 — anatomy of a rack</strong>, which starts with the fact that a "42U cabinet" does not give you 42U of usable space.</p>]]></content:encoded>
  </item>
  <item>
    <title>Linux Networking Part 7: The Cheat Sheet</title>
    <link>https://justmeandlinux.com/net-07-cheat-sheet/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/net-07-cheat-sheet/</guid>
    <pubDate>Sat, 15 Aug 2026 13:00:00 GMT</pubDate>
    <category>Linux</category>
    <description>The whole series on one page. Print it, pin it, put it in your runbook. Parts 0 through 6 explain why each of these works. This is the lookup table for when you don&apos;t have time to read. Glossary Term Meaning Interface A network device as the kernel sees it …</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<p>The whole series on one page. Print it, pin it, put it in your runbook.</p>
<p>Parts 0 through 6 explain <em>why</em> each of these works. This is the lookup table for when you don't have time to read.</p>
<h2 id="glossary">Glossary</h2>
<table>
<thead>
<tr>
<th>Term</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Interface</strong></td>
<td>A network device as the kernel sees it — <code>eth0</code>, <code>lo</code>, <code>docker0</code>. Physical or virtual.</td>
</tr>
<tr>
<td><strong>Loopback</strong></td>
<td><code>lo</code> / <code>127.0.0.1</code>. Traffic to the machine itself. Never reaches a wire.</td>
</tr>
<tr>
<td><strong>CIDR</strong></td>
<td><code>192.0.2.0/24</code> — first 24 bits are the network. Same subnet = reachable directly at layer 2.</td>
</tr>
<tr>
<td><strong>Gateway</strong></td>
<td>Where packets go when the destination isn't in a connected subnet.</td>
</tr>
<tr>
<td><strong>Port</strong></td>
<td>16-bit service identifier, 1–65535. Not a physical thing.</td>
</tr>
<tr>
<td><strong>Ephemeral port</strong></td>
<td>The high-range pool the kernel picks from for <em>outbound</em> connections. Finite — see exhaustion.</td>
</tr>
<tr>
<td><strong>Socket</strong></td>
<td>Kernel structure for one endpoint. Owns a receive buffer and a send buffer.</td>
</tr>
<tr>
<td><strong>4-tuple</strong></td>
<td><code>srcIP:srcPort → dstIP:dstPort</code>. The unique identity of a TCP connection.</td>
</tr>
<tr>
<td><strong>Listening vs established</strong></td>
<td>One listener on :443 can carry 50,000 established connections.</td>
</tr>
<tr>
<td><strong>TCP</strong></td>
<td>Reliable, ordered, connection-oriented. A <strong>byte stream</strong>, not messages.</td>
</tr>
<tr>
<td><strong>UDP</strong></td>
<td>Messages preserved, no delivery guarantee, no retransmission, no ordering.</td>
</tr>
<tr>
<td><strong>ICMP</strong></td>
<td>Control/diagnostic protocol. <strong>No ports.</strong> Blocking it breaks PMTU discovery.</td>
</tr>
<tr>
<td><strong>Frame / packet / segment / datagram</strong></td>
<td>Same data at L2 / L3 / TCP / UDP.</td>
</tr>
<tr>
<td><strong>MAC address</strong></td>
<td>Layer 2 address. Only meaningful within one segment.</td>
</tr>
<tr>
<td><strong>ARP</strong></td>
<td>Discovers the MAC for an IP on the local segment.</td>
</tr>
<tr>
<td><strong>MTU / MSS</strong></td>
<td>Largest packet on an interface (usually 1500) / largest TCP payload per segment.</td>
</tr>
<tr>
<td><strong>RTT</strong></td>
<td>Round-trip time.</td>
</tr>
<tr>
<td><strong>TTL</strong></td>
<td>Hop counter in the IP header. Hits zero → ICMP time exceeded. How traceroute works.</td>
</tr>
<tr>
<td><strong>NAT</strong></td>
<td>Rewriting addresses in flight. Your router does it. So does Docker.</td>
</tr>
<tr>
<td><strong>Accept queue</strong></td>
<td>Completed handshakes waiting for the app to call <code>accept()</code>.</td>
</tr>
<tr>
<td><strong>SYN queue</strong></td>
<td>Half-open connections, handshake in progress.</td>
</tr>
<tr>
<td><strong>Congestion window (cwnd)</strong></td>
<td>How much data TCP will keep in flight before waiting for acks.</td>
</tr>
<tr>
<td><strong>Network namespace</strong></td>
<td>An independent copy of the whole network stack. Every container has one.</td>
</tr>
<tr>
<td><strong>veth pair</strong></td>
<td>Two virtual interfaces acting as a pipe — one end in a container, one on the host.</td>
</tr>
</tbody>
</table>
<h2 id="start-here-three-questions">Start here: three questions</h2>
<p>Decide which one you're asking before you type anything.</p>
<ol>
<li><strong>Can they reach me?</strong> → connectivity. <code>nc -zv</code>, <code>ss -lnt</code>, <code>tcpdump</code>, firewall rules, <code>ip route get</code></li>
<li><strong>Are we losing traffic?</strong> → loss. <code>nstat</code>, <code>ip -s link</code>, <code>/proc/net/softnet_stat</code>, <code>ethtool -S</code></li>
<li><strong>Why is it slow?</strong> → latency. <code>curl -w</code>, <code>ss -ti</code>, <code>mtr</code>, <code>iostat -x</code>, <code>mpstat</code></li>
</ol>
<p>Mixing these up costs hours. Answer this first, every time.</p>
<h2 id="the-ladder">The ladder</h2>
<p>Every tool reads one layer. Know which.</p>
<pre data-lang-none><code> [7] application ......... ss Recv-Q, app logs, app metrics
 [6] socket buffers ...... ss -tin
 [5] TCP/IP stack ........ nstat
 [4] routing ............. ip route get, ip rule, rp_filter
 [3] netfilter/conntrack . iptables -L -n -v, nft list ruleset, conntrack -S
 [2] driver/softirq ...... /proc/net/softnet_stat, /proc/interrupts
 [1] NIC hardware ........ ip -s link, ethtool -S, ethtool -g
 [0] layer 2 ............. ip neigh, bridge fdb show
     the wire
</code></pre>
<h2 id="the-flowchart">The flowchart</h2>
<pre data-lang-none><code>                  "it doesn't work"
                          |
              +-----------+-----------+
              |                       |
        fails INSTANTLY          HANGS then times out
     ("connection refused")     ("connection timed out")
              |                       |
        packet ARRIVED           packet may not have arrived
              |                       |
     +--------+--------+         tcpdump on the server
     |                 |              |
  nothing         firewall     +------+------+
  listening       REJECT       |             |
     |                    SYN visible?   nothing at all?
  ss -lntp                    |             |
  check bind addr        LOCAL problem   UPSTREAM problem
  (127.0.0.1?)           firewall -Z     routing / secgroup
                         rp_filter       ip route get
                         conntrack       ip neigh
                         accept queue


              "it works but it's slow"
                          |
                    curl -w breakdown
                          |
      +-------------------+-------------------+
      |                   |                   |
  high DNS          high CONNECT          high TTFB
      |                   |                   |
  dig / getent      network path OR      the APPLICATION
  resolvectl        accept queue         (and what's behind it)
                         |                     |
                   ss -lnt Recv-Q       app logs, upstream time
                   ss -ti rtt/retrans   iostat -x, mpstat
                   mtr (final hop only)
</code></pre>
<h2 id="the-60-second-triage">The 60-second triage</h2>
<p>On an unfamiliar box, in this order:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nstat</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                              # run twice, 30s apart — what is moving?</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> link</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> show</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth0</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">               # is the wire healthy?</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -lnt</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                            # are listeners keeping up? (Recv-Q vs Send-Q)</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">cat</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /proc/net/softnet_stat</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">         # is one CPU drowning? col 2 = drops</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">conntrack</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -S</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                       # if this box does NAT or containers</span></span>
<span class="line"></span></code></pre>
<p>Five commands. Most of the search space eliminated.</p>
<h2 id="first-contact-%E2%80%94-is-it-even-reachable">First contact — is it even reachable?</h2>
<p>Bottom-up. Each step assumes the previous one passed.</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -br</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> addr</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                       # interface up? right address? right prefix?</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -br</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> link</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> route</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> get</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 10.0.0.50</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">            # route, interface, AND source IP</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ping</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -c</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 5</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 10.0.0.50</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">               # see caveat below</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getent</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> hosts</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> db.internal</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">          # resolves the way the APP sees it (NSS)</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">dig</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> +short</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> db.internal</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">            # what DNS itself says</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nc</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -zv</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -w</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 3</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 10.0.0.50</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 5432</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        # is the port actually open?</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -vI</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> https://example.com/</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">     # TLS + HTTP</span></span>
<span class="line"></span></code></pre>
<p><strong>Ping failing means almost nothing</strong> — ICMP is blocked by policy everywhere. Ping succeeding is useful; ping failing is not evidence the host is down. Test the port.</p>
<p><strong><code>nc</code> failure modes are the diagnosis:</strong></p>
<ul>
<li>Refused <strong>instantly</strong> → packet arrived, nothing listening. Check bind address.</li>
<li><strong>Hangs</strong> then times out → something is silently dropping. Firewall.</li>
</ul>
<p><strong>DNS — the split that solves the hard cases:</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">dig</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> +short</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> name</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">       # queries DNS directly, BYPASSES /etc/hosts and NSS</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getent</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> hosts</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> name</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">     # goes through NSS — same path as your application</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">dig</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> @8.8.8.8</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> name</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">     # is it my resolver, or the record itself?</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">dig</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> +trace</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> name</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">       # follow delegation from the root</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">resolvectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> status</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">     # systemd-resolved config</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">grep</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ^hosts</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /etc/nsswitch.conf</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">cat</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /etc/resolv.conf</span></span>
<span class="line"></span></code></pre>
<p><code>dig</code> works but <code>getent</code> fails → <strong>not DNS.</strong> It's <code>/etc/hosts</code>, <code>nsswitch.conf</code>, or the resolver library.</p>
<p><strong>Who owns the port / what is the process doing:</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -lntp</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">lsof</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> :443</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">lsof</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -P</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">fuser</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 443/tcp</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">strace</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -e</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> trace=network</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -p</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">pi</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">d</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">     # is it even TRYING to connect?</span></span>
<span class="line"></span></code></pre>
<p><strong>TLS:</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">openssl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> s_client</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -connect</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> example.com:443</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -servername</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> example.com</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">echo</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> openssl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> s_client</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -connect</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> example.com:443</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -servername</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> example.com</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> 2></span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/dev/null</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> openssl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> x509</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -noout</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -dates</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -subject</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -issuer</span></span>
<span class="line"></span></code></pre>
<p><strong>Always pass <code>-servername</code></strong> or you'll get the wrong certificate and debug a problem you created.</p>
<p><strong>Bandwidth (not latency):</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">iperf3</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                    # server</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">iperf3</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -c</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">i</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">p</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">               # client</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">iperf3</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -c</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">i</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">p</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -R</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">            # reverse direction — always test both</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">iperf3</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -c</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">i</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">p</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -P</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 4</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">          # parallel streams</span></span>
<span class="line"></span></code></pre>
<p>One stream slow, four streams fast = window/RTT ceiling, <strong>not</strong> a slow link.</p>
<p><strong>traceroute caveats:</strong> <code>* * *</code> means the router declined to reply, not a broken hop. Intermediate-hop loss is usually ICMP rate limiting. Only the <strong>final hop</strong>, or loss persisting through every subsequent hop, is real. <code>traceroute -T -p 443</code> gets further through firewalls than the UDP default.</p>
<h2 id="symptom-%E2%86%92-command">Symptom → command</h2>
<table>
<thead>
<tr>
<th>Symptom</th>
<th>Run this</th>
<th>Looking for</th>
</tr>
</thead>
<tbody>
<tr>
<td>"Connection refused", instant</td>
<td><code>ss -lntp</code></td>
<td>Not listening, or bound to <code>127.0.0.1</code></td>
</tr>
<tr>
<td>"Connection timed out", slow</td>
<td><code>tcpdump -nn 'host X'</code></td>
<td>SYN retransmits with no reply = firewall DROP</td>
</tr>
<tr>
<td>Intermittent connect hangs</td>
<td><code>ss -lnt</code> + <code>nstat | grep -i listen</code></td>
<td>Accept queue full, <code>ListenOverflows</code></td>
</tr>
<tr>
<td>Transfers stall and resume</td>
<td><code>tcpdump</code> for <code>win 0</code></td>
<td>Receiver not reading</td>
</tr>
<tr>
<td>Slow, but only large requests</td>
<td><code>ping -M do -s 1472 X</code>, <code>tracepath X</code></td>
<td>MTU blackhole</td>
</tr>
<tr>
<td>Connections reset mid-session</td>
<td><code>tcpdump -nn 'tcp[tcpflags] &amp; tcp-rst != 0'</code></td>
<td>Middlebox idle timeout, app crash</td>
</tr>
<tr>
<td>Losing UDP silently</td>
<td><code>nstat -az | grep -i Udp</code></td>
<td><code>UdpRcvbufErrors</code></td>
</tr>
<tr>
<td>Box "can't keep up"</td>
<td><code>ip -s link</code>, <code>/proc/net/softnet_stat</code></td>
<td>overruns, softnet col 2</td>
</tr>
<tr>
<td>Low CPU overall, drops anyway</td>
<td><code>/proc/net/softnet_stat</code>, <code>/proc/interrupts</code></td>
<td>One CPU handling all IRQs</td>
</tr>
<tr>
<td>Connections fail, box idle</td>
<td><code>ss -tan state time-wait | wc -l</code></td>
<td>Ephemeral port exhaustion</td>
</tr>
<tr>
<td>Everything broke at 3am</td>
<td><code>sar -n EDEV -f /var/log/sa/saNN</code></td>
<td>Historical error rates</td>
</tr>
<tr>
<td>FD-related failures</td>
<td><code>cat /proc/&lt;pid&gt;/limits</code>, <code>ss -antp state close-wait</code></td>
<td>Limit, or descriptor leak</td>
</tr>
<tr>
<td>Name won't resolve for the app, <code>dig</code> is fine</td>
<td><code>getent hosts X</code>, <code>/etc/nsswitch.conf</code></td>
<td>NSS or <code>/etc/hosts</code>, not DNS</td>
</tr>
<tr>
<td>Packet arrives, vanishes, no counters move</td>
<td><code>nstat -az | grep IPReversePathFilter</code></td>
<td>rp_filter dropping asymmetric traffic</td>
</tr>
<tr>
<td>Can't reach a host on the same subnet</td>
<td><code>ip neigh show</code></td>
<td><code>INCOMPLETE</code> = no ARP reply</td>
</tr>
<tr>
<td>Works, then doesn't, on one IP</td>
<td><code>arping -I eth0 &lt;ip&gt;</code></td>
<td>Two MACs replying = duplicate IP</td>
</tr>
<tr>
<td>Intermittent on a big flat network</td>
<td><code>dmesg | grep 'neighbour table overflow'</code></td>
<td><code>gc_thresh3</code> hit</td>
</tr>
<tr>
<td>Firewall suspected</td>
<td><code>iptables -Z</code>, reproduce, <code>iptables -L -n -v</code></td>
<td>Which counter moved</td>
</tr>
<tr>
<td><code>iptables -L</code> empty but filtering happens</td>
<td><code>nft list ruleset</code></td>
<td>Rules live in nftables</td>
</tr>
<tr>
<td>Published container port dead</td>
<td><code>iptables -t nat -L -n -v</code></td>
<td>DNAT rule counters</td>
</tr>
<tr>
<td>Host tools show nothing for a container</td>
<td><code>nsenter -t &lt;pid&gt; -n &lt;cmd&gt;</code></td>
<td>Wrong namespace</td>
</tr>
<tr>
<td>nginx→php-fpm invisible to tcpdump</td>
<td><code>ss -xlp</code></td>
<td>Unix socket — no packets exist</td>
</tr>
</tbody>
</table>
<h2 id="ss-%E2%80%94-sockets">ss — sockets</h2>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -lntp</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                    # what am I listening on, and which process</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -antp</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                    # every TCP connection with process</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                       # summary counts by protocol and state</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -tin</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                     # per-connection TCP internals (rtt, cwnd, retrans)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -antp</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dst</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 203.0.113.7</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">               # one peer</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -antp</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '( dport = :443 or sport = :443 )'</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -antp</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> state</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> established</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -antp</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> state</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> close-wait</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">              # app leaking descriptors</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -tan</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> state</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> time-wait</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> wc</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -l</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Top clients by connection count</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -ant</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> state</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> established</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> awk</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '{print $5}'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> cut</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -d:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f1</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> sort</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> uniq</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -c</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> sort</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -rn</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> head</span></span>
<span class="line"></span></code></pre>
<p><strong>Always <code>-n</code>.</strong> Without it, <code>ss</code> does a DNS lookup per address.</p>
<h3 id="reading-the-queue-columns">Reading the queue columns</h3>
<p><strong>ESTABLISHED socket:</strong></p>
<ul>
<li>Recv-Q = bytes waiting for the app to <code>read()</code> → <strong>app is behind</strong></li>
<li>Send-Q = bytes waiting to be acked → <strong>network or peer is behind</strong></li>
</ul>
<p><strong>LISTENING socket:</strong></p>
<ul>
<li>Recv-Q = connections waiting for <code>accept()</code></li>
<li>Send-Q = maximum accept queue size</li>
<li>Recv-Q at Send-Q = <strong>overflowing right now</strong></li>
</ul>
<h3 id="ssti-fields"><code>ss -ti</code> fields</h3>
<table>
<thead>
<tr>
<th>Field</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>rtt:X/Y</code></td>
<td>smoothed RTT / variance, in ms</td>
</tr>
<tr>
<td><code>minrtt</code></td>
<td>best RTT seen — compare with <code>rtt</code> to spot queueing</td>
</tr>
<tr>
<td><code>retrans:A/B</code></td>
<td>outstanding / total retransmits — <strong>evidence of loss</strong></td>
</tr>
<tr>
<td><code>cwnd</code></td>
<td>congestion window in segments</td>
</tr>
<tr>
<td><code>mss</code>, <code>pmtu</code></td>
<td>segment size, path MTU</td>
</tr>
<tr>
<td><code>lastsnd</code>/<code>lastrcv</code>/<code>lastack</code></td>
<td>ms since last activity</td>
</tr>
<tr>
<td><code>bytes_sent</code>/<code>bytes_acked</code>/<code>bytes_received</code></td>
<td>lifetime totals</td>
</tr>
</tbody>
</table>
<h3 id="tcp-states">TCP states</h3>
<table>
<thead>
<tr>
<th>State</th>
<th>Pile-up means</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>SYN-SENT</code></td>
<td>Can't reach the destination</td>
</tr>
<tr>
<td><code>SYN-RECV</code></td>
<td>SYN flood, or clients vanishing mid-handshake</td>
</tr>
<tr>
<td><code>CLOSE-WAIT</code></td>
<td><strong>Your app isn't calling <code>close()</code>. Application bug.</strong></td>
</tr>
<tr>
<td><code>FIN-WAIT-2</code></td>
<td>Peer isn't closing its end</td>
</tr>
<tr>
<td><code>TIME-WAIT</code></td>
<td>Usually normal; check if you're exhausting ports</td>
</tr>
</tbody>
</table>
<h2 id="counters">Counters</h2>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nstat</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                       # delta since last run — use this during incidents</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nstat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -az</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                   # everything, including zeros</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nstat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -d</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                  # sample every second</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nstat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -az</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> retrans</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nstat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -az</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> listen</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nstat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -az</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Udp</span></span>
<span class="line"></span></code></pre>
<table>
<thead>
<tr>
<th>Counter</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>TcpRetransSegs</code></td>
<td>Path is losing packets</td>
</tr>
<tr>
<td><code>TcpExtTCPSynRetrans</code></td>
<td>Connection <em>setup</em> failing</td>
</tr>
<tr>
<td><code>TcpExtTCPTimeouts</code></td>
<td>RTO fired — no ack at all</td>
</tr>
<tr>
<td><code>TcpExtListenOverflows</code></td>
<td><strong>Accept queue full</strong></td>
</tr>
<tr>
<td><code>TcpExtListenDrops</code></td>
<td>All listening-socket drops</td>
</tr>
<tr>
<td><code>TcpExtTCPBacklogDrop</code></td>
<td>App held the socket lock too long</td>
</tr>
<tr>
<td><code>UdpRcvbufErrors</code></td>
<td><strong>Silent UDP loss — check this on DNS/syslog/statsd</strong></td>
</tr>
<tr>
<td><code>TcpOutRsts</code></td>
<td>We are resetting connections</td>
</tr>
<tr>
<td><code>TcpEstabResets</code></td>
<td>Established connections killed by RST</td>
</tr>
</tbody>
</table>
<p>Names vary by kernel version. <code>nstat -az</code> on your box is authoritative.</p>
<h2 id="interface-and-driver">Interface and driver</h2>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> link</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> show</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth0</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        # errors, dropped, overrun/missed</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> link</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> show</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth0</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">     # more detail</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ethtool</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -S</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth0</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -E</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'drop|err|miss|discard|fifo'</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ethtool</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -g</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth0</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">             # ring buffer sizes (current vs max)</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ethtool</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -k</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth0</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">             # offloads — GRO/TSO make tcpdump show huge packets</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ethtool</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth0</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                # link speed, duplex</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tc</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> qdisc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> show</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dev</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth0</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">   # transmit-side drops</span></span>
<span class="line"></span></code></pre>
<p><strong>errors</strong> = physical layer. Cable, SFP, port, duplex.<br>
<strong>overrun / missed</strong> = ring buffer overflow. The box can't keep up.<br>
<strong>dropped</strong> = kernel discarded it. Usually no buffer or no handler.</p>
<p><code>ethtool -S</code> counter names are <strong>driver-specific</strong>. Grep, don't memorise.</p>
<h2 id="softirq">Softirq</h2>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">cat</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /proc/net/softnet_stat</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">      # hex, one line per CPU</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">watch</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -d</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> cat</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /proc/net/softnet_stat</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">cat</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /proc/interrupts</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">mpstat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -P</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ALL</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                 # %soft column</span></span>
<span class="line"></span></code></pre>
<ul>
<li>Column 1 = processed. <strong>Wildly uneven across CPUs = IRQ imbalance.</strong></li>
<li>Column 2 = dropped (backlog full). Non-zero is a real, invisible drop.</li>
<li>Column 3 = time_squeeze. NAPI budget exhausted.</li>
</ul>
<h2 id="tcpdump">tcpdump</h2>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tcpdump</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth0</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -nn</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -c</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 100</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'host 203.0.113.7 and port 443'</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tcpdump</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth0</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -nn</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -w</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /tmp/cap.pcap</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'host 203.0.113.7'</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tcpdump</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -r</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /tmp/cap.pcap</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -nn</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> head</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -50</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tcpdump</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -nn</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'tcp[tcpflags] &#x26; tcp-syn != 0'</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    # who is connecting</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tcpdump</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -nn</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'tcp[tcpflags] &#x26; tcp-rst != 0'</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    # who is being reset</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tcpdump</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -nn</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'host X and not port 22'</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">          # exclude your own SSH</span></span>
<span class="line"></span></code></pre>
<p>Key flags: <code>-nn</code> (no resolution), <code>-c N</code> (stop after N), <code>-w</code>/<code>-r</code> (file), <code>-e</code> (ethernet header), <code>-A</code>/<code>-X</code> (payload), <code>-S</code> (absolute seq), <code>-tttt</code> (readable timestamps).</p>
<p><strong>tcpdump sees inbound packets before netfilter.</strong> So: SYN visible but app never sees it = local firewall. SYN not visible at all = never arrived.</p>
<h3 id="flags">Flags</h3>
<table>
<thead>
<tr>
<th>Notation</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>[S]</code></td>
<td>SYN</td>
</tr>
<tr>
<td><code>[S.]</code></td>
<td>SYN+ACK</td>
</tr>
<tr>
<td><code>[.]</code></td>
<td>ACK only</td>
</tr>
<tr>
<td><code>[P.]</code></td>
<td>PSH+ACK (data)</td>
</tr>
<tr>
<td><code>[F.]</code></td>
<td>FIN+ACK</td>
</tr>
<tr>
<td><code>[R]</code> / <code>[R.]</code></td>
<td>RST</td>
</tr>
</tbody>
</table>
<p><strong>The dot is ACK.</strong> That's the whole notation.</p>
<h3 id="patterns">Patterns</h3>
<table>
<thead>
<tr>
<th>What you see</th>
<th>Diagnosis</th>
</tr>
</thead>
<tbody>
<tr>
<td>SYN repeated at 1s, 2s, 4s — silence</td>
<td>Firewall <strong>DROP</strong></td>
</tr>
<tr>
<td>SYN → immediate RST</td>
<td>Nothing listening, or <strong>REJECT</strong></td>
</tr>
<tr>
<td>Handshake completes, SYN-ACK repeats</td>
<td><strong>Accept queue full</strong></td>
</tr>
<tr>
<td>Duplicate ACKs → retransmit</td>
<td>Real path loss</td>
</tr>
<tr>
<td><code>win 0</code></td>
<td>Receiver's buffer full</td>
</tr>
<tr>
<td>RST mid-connection, after a round idle interval</td>
<td>Middlebox timeout</td>
</tr>
<tr>
<td>FIN, ACK, FIN, ACK</td>
<td>Healthy close</td>
</tr>
<tr>
<td>Small packets fine, large ones stall forever</td>
<td><strong>MTU blackhole + blocked ICMP</strong></td>
</tr>
</tbody>
</table>
<p><strong>Window scale trap:</strong> if you didn't capture the handshake, every <code>win</code> value printed is wrong. Recapture from connection start.</p>
<h2 id="latency">Latency</h2>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -w</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '\ndns %{time_namelookup}\nconnect %{time_connect}\ntls %{time_appconnect}\nttfb %{time_starttransfer}\ntotal %{time_total}\n'</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  -o</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /dev/null</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> https://example.com/</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ping</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -c</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 20</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> X</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                # min/avg/max/mdev</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">mtr</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -rwc</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 100</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> X</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">              # per-hop loss and latency</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -tin</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> state</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> established</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dst</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> X</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    # real RTT on the real connection</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">iostat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -x</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                 # r_await / w_await, NOT %util</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">mpstat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -P</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ALL</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">             # %soft</span></span>
<span class="line"></span></code></pre>
<p><strong>Values are cumulative — subtract to get each phase.</strong></p>
<ul>
<li>High connect, low TTFB → <strong>network</strong></li>
<li>Low connect, high TTFB → <strong>application</strong></li>
<li>Erratic connect → <strong>queueing or loss</strong></li>
</ul>
<p><code>mtr</code> caveat: loss at intermediate hops is usually ICMP rate limiting, not real. Only trust loss at the <strong>final hop</strong>, or loss that persists through every subsequent hop.</p>
<p><code>iostat</code> caveat: on SSD/NVMe, <code>%util</code> does not mean saturation. Use <code>await</code>.</p>
<p>Throughput ceiling ≈ <strong>window / RTT</strong>. 64KB over 100ms RTT ≈ 5 Mbit/s no matter how fast the link.</p>
<h2 id="historical-data">Historical data</h2>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sar</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> DEV</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                    # throughput</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sar</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> EDEV</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                   # errors and drops</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sar</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> TCP,ETCP</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">               # connection rates, retransmits</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sar</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> SOCK</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                   # socket counts</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sar</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> EDEV</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /var/log/sa/sa13</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sar</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> DEV</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 03:00:00</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -e</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 04:00:00</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /var/log/sa/sa13</span></span>
<span class="line"></span></code></pre>
<p>Log path varies by distro (<code>/var/log/sa</code> or <code>/var/log/sysstat</code>). <strong>On Debian/Ubuntu the collector is often disabled by default</strong> — check <code>/etc/default/sysstat</code>.</p>
<p>Enable this on every production box. Every other tool tells you about now; incidents are about twenty minutes ago.</p>
<h2 id="routing-arp-firewalls-containers">Routing, ARP, firewalls, containers</h2>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> route</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> get</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 10.0.0.50</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        # ASK the kernel. Gives via, dev, AND src.</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> route</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                      # the table — most specific prefix wins</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> rule</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> show</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                  # which table gets consulted, lowest number first</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> route</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> show</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> table</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 100</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sysctl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -a</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> 2></span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/dev/null</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '\.rp_filter'</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    # 0=off 1=strict 2=loose</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nstat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -az</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> IPReversePathFilter</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">          # proof rp_filter is dropping</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> neigh</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> show</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                 # REACHABLE / STALE / INCOMPLETE</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> neigh</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> flush</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dev</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth0</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">arping</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -I</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth0</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 192.0.2.1</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sysctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> net.ipv4.neigh.default.gc_thresh3</span></span>
<span class="line"></span></code></pre>
<p><strong><code>ip route get</code> over reading the table.</strong> It runs the real decision and reports the source IP that will be used — which is what the far end's firewall sees.</p>
<p><strong><code>INCOMPLETE</code> in <code>ip neigh</code></strong> = ARP sent, nobody answered. Below every other layer in this series.</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">iptables</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -L</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -v</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --line-numbers</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">iptables</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> nat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -L</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -v</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">          # container hosts: check nat BEFORE filter</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">iptables</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -Z</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                       # zero, reproduce, then look</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nft</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> list</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ruleset</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">conntrack</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -L</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ; </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">conntrack</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -E</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ; </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">conntrack</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -S</span></span>
<span class="line"></span></code></pre>
<p><strong>Zero, reproduce, look.</strong> <code>iptables -Z</code>, trigger the problem, <code>iptables -L -n -v</code> — whichever counter moved is the rule that handled your packet. Stop reasoning about rule order; measure it.</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> netns</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> list</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                              # often EMPTY on Docker — not a bug</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> inspect</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '{{.State.Pid}}'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">nam</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">e</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nsenter</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">pi</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">d</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -lntp</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nsenter</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">pi</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">d</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ip</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -br</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> addr</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nsenter</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">pi</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">d</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tcpdump</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -nn</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth0</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nsenter</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">pi</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">d</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> link</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> show</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth0</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">      # shows eth0@ifN</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> link</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '^N:'</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                       # N = the host-side veth</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">bridge</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> link</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ; </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">bridge</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> fdb</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> show</span></span>
<span class="line"></span></code></pre>
<p><strong><code>nsenter -t &lt;pid&gt; -n &lt;cmd&gt;</code> runs your host tools inside the container's namespace.</strong> You never need to install debugging tools in an image.</p>
<p><strong><code>eth0@ifN</code> inside the container → interface index N on the host.</strong> That's which veth to capture on.</p>
<p>Namespaces are independent stacks: host <code>ss</code>, <code>tcpdump</code>, <code>iptables</code> and every counter in part 3 do <strong>not</strong> cover container traffic.</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -xlp</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                       # listening unix sockets + process</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -xp</span></span>
<span class="line"></span></code></pre>
<p>Unix sockets have <strong>no packets</strong> — <code>tcpdump</code> will never show them. <code>(13: Permission denied)</code> is a file-permission problem, not a network one.</p>
<h2 id="config-worth-checking">Config worth checking</h2>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sysctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> net.core.somaxconn</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                  # accept queue ceiling</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sysctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> net.ipv4.tcp_max_syn_backlog</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        # SYN queue</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sysctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> net.core.netdev_max_backlog</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">         # softirq backlog</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sysctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> net.ipv4.ip_local_port_range</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        # outbound port pool</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sysctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> net.ipv4.tcp_syncookies</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sysctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> net.ipv4.tcp_congestion_control</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sysctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> net.netfilter.nf_conntrack_max</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sysctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> net.netfilter.nf_conntrack_count</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">cat</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /proc/</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">pi</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">d</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/limits</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                     # the FD limit that actually applies</span></span>
<span class="line"></span></code></pre>
<p><strong>Rules on tuning:</strong></p>
<ul>
<li>Accept queue = <code>min(listen() backlog, net.core.somaxconn)</code>. Raising one alone does nothing.</li>
<li><strong>Never</strong> <code>net.ipv4.tcp_tw_recycle</code>. Removed in kernel 4.12; broke NAT'd clients before that. Any guide recommending it predates 2017.</li>
<li>Buffer tuning (<code>tcp_rmem</code>/<code>tcp_wmem</code>) matters for high bandwidth-delay paths. It's cargo cult for a web server 20ms from its clients.</li>
<li>A full accept queue is a <strong>symptom</strong>. A deeper queue is somewhere deeper to fall behind.</li>
</ul>
<h2 id="nginx-specifics">nginx specifics</h2>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tail</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -100</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /var/log/nginx/error.log</span></span>
<span class="line"></span></code></pre>
<table>
<thead>
<tr>
<th>Message</th>
<th>Means</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>worker_connections are not enough</code></td>
<td>Raise <code>worker_connections</code></td>
</tr>
<tr>
<td><code>accept4() failed (24: Too many open files)</code></td>
<td>FD limit — <code>worker_rlimit_nofile</code>, systemd <code>LimitNOFILE</code></td>
</tr>
<tr>
<td><code>upstream timed out</code></td>
<td>The backend is slow, not nginx</td>
</tr>
</tbody>
</table>
<p>Add to <code>log_format</code> and split the time:</p>
<ul>
<li><code>$request_time</code> — total</li>
<li><code>$upstream_connect_time</code> — connecting to backend</li>
<li><code>$upstream_header_time</code> — until backend headers</li>
<li><code>$upstream_response_time</code> — total backend time</li>
</ul>
<p><code>$upstream_response_time</code> ≈ <code>$request_time</code> → <strong>the backend is the problem</strong>, run the same investigation one layer down.</p>
<p>Use <code>keepalive</code> in <code>upstream</code> blocks for high-volume proxying, or you'll exhaust source ports. Configuration details in <a href="https://nginx.org/en/docs/http/ngx_http_upstream_module.html">the nginx upstream docs</a>.</p>
<h2 id="learning-path">Learning path</h2>
<p>Honest sequencing. Don't skip.</p>
<p><strong>Stage 0 — the floor (a few days)</strong></p>
<p>Glossary above, cold. The seven-step reachability order: address → route → ping → DNS → port → TLS → HTTP. Learn that ping failing proves nothing and <code>nc -zv</code> is the real test. Learn why <code>dig</code> and <code>getent</code> can disagree. Read <code>man ip</code>, <code>man dig</code>, <code>man nc</code>.</p>
<p><strong>Stage 1 — the model (a week)</strong></p>
<p>The 4-tuple. Socket buffers. The TCP handshake and teardown. Stream vs datagram. Draw the seven-layer ladder from memory. <strong>Capture a healthy request with tcpdump and read every field.</strong> You cannot recognise broken without knowing healthy.</p>
<p><strong>Stage 2 — the socket layer (a month of using it)</strong></p>
<p>Live in <code>ss</code>. <code>ss -lntp</code> and <code>ss -antp</code> until they're muscle memory. Learn what Recv-Q and Send-Q mean in both contexts. Learn the states, especially <code>CLOSE_WAIT</code>. Read <code>man ss</code> properly — it's short.</p>
<p><strong>Stage 3 — evidence (a month)</strong></p>
<p><code>nstat</code>, <code>ip -s link</code>, <code>ethtool -S</code>, <code>/proc/net/softnet_stat</code>. The goal is one skill: <strong>make a falsifiable claim.</strong> Not "we have packet loss" but "<code>ListenOverflows</code> went 0 → 14,000 in ten minutes while interface errors stayed at zero." Learn the difference between a drop and a retransmit until it's automatic.</p>
<p><strong>Stage 4 — the wire (ongoing)</strong></p>
<p><code>tcpdump</code> filters, then the eight patterns above. Capture on both ends of a connection at once and compare — that's the highest-value habit in this entire series. Then Wireshark for anything complex.</p>
<p><strong>Stage 5 — attribution (ongoing)</strong></p>
<p><code>curl -w</code> decomposition. Path vs application. <code>iostat</code>, <code>mpstat</code>, and understanding that application and disk latency surface as network symptoms. Work a real production problem end to end.</p>
<p><strong>Stage 6 — routing, L2 and containers (a month)</strong></p>
<p><code>ip route get</code> and <code>ip rule</code>. rp_filter and asymmetric routing. <code>ip neigh</code> states. Reading firewall rule counters with the zero-reproduce-look technique. Then namespaces: <code>nsenter -t &lt;pid&gt; -n</code>, veth pairs, and where container traffic gets NAT'd. If you run containers, this stage is not optional.</p>
<p><strong>Stage 7 — eBPF</strong></p>
<p><code>bpftrace</code>, and the BCC tools: <code>tcpretrans</code>, <code>tcplife</code>, <code>tcpconnect</code>, <code>biolatency</code>. Move from "0.3% of connections retransmitted" to "<em>this</em> connection retransmitted, at <em>this</em> moment." This is the line between competent and strong.</p>
<p><strong>Reading, in order</strong></p>
<ol>
<li><code>man 7 tcp</code> — dense, unglamorous, better than most books</li>
<li><code>man ss</code>, <code>man tcpdump</code>, <code>man pcap-filter</code></li>
<li><em>TCP/IP Illustrated, Volume 1</em> (Stevens / Fall) — nothing else builds the same depth</li>
<li><code>Documentation/networking/</code> in the kernel source</li>
<li><a href="https://github.com/iovisor/bcc">BCC</a> and <a href="https://github.com/bpftrace/bpftrace">bpftrace</a></li>
</ol>
<h2 id="the-five-things-to-remember-if-you-forget-everything-else">The five things to remember if you forget everything else</h2>
<ol>
<li><strong>Everything is a queue.</strong> Full queue = drops. Deep queue = latency. Find the queue.</li>
<li><strong>Drops and retransmits point in opposite directions.</strong> Drops mean <em>this box</em> discarded something. Retransmits mean <em>the path</em> lost something.</li>
<li><strong>Fast failure and slow failure are different diagnoses.</strong> Refused = it arrived. Timeout = it didn't.</li>
<li><strong><code>ss -lnt</code> before <code>tcpdump</code>.</strong> Recv-Q at Send-Q on a listener answers more incidents than any packet capture.</li>
<li><strong>Localise, then tune.</strong> Every sysctl is a guess until you know which layer is failing.</li>
<li><strong>The application is part of the network stack.</strong> Most "network problems" are an app that stopped reading its sockets.</li>
</ol>
<hr>
<p>Compiled by AI. Proofread by caffeine. ☕</p>]]></content:encoded>
  </item>
  <item>
    <title>Linux Networking Part 6: Routing, ARP, Firewalls and Containers</title>
    <link>https://justmeandlinux.com/net-06-routing-firewalls-containers/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/net-06-routing-firewalls-containers/</guid>
    <pubDate>Sat, 15 Aug 2026 12:00:00 GMT</pubDate>
    <category>Linux</category>
    <description>Five parts in, we&apos;ve covered sockets, counters, packets and latency. There&apos;s a gap, and it&apos;s the one that eats the most time on modern infrastructure: the packet reaches the box and disappears. No socket sees it. No drop counter moves. tcpdump shows it…</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<p>Five parts in, we've covered sockets, counters, packets and latency. There's a gap, and it's the one that eats the most time on modern infrastructure: <strong>the packet reaches the box and disappears.</strong></p>
<p>No socket sees it. No drop counter moves. <code>tcpdump</code> shows it arriving and then nothing. It was routed somewhere unexpected, discarded by a firewall rule, dropped by reverse path filtering, or delivered into a network namespace you didn't know existed.</p>
<p>This part covers stops 3 and 4 on the ladder — netfilter and routing — plus the layer 2 and container material the series has been quietly assuming.</p>
<h2 id="routing-ask-dont-read">Routing: ask, don't read</h2>
<p>Everyone starts with <code>ip route</code>. Wrong instinct. Start with:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> route</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> get</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 10.0.0.50</span></span>
<span class="line"></span></code></pre>
<pre data-lang-none><code>10.0.0.50 via 192.0.2.1 dev eth0 src 192.0.2.2 uid 0
    cache
</code></pre>
<p>This asks the kernel to run its actual routing decision and report the result. No interpreting a table, no working out which prefix is more specific. Three answers:</p>
<ul>
<li><strong><code>via</code></strong> — the next hop</li>
<li><strong><code>dev</code></strong> — the outbound interface</li>
<li><strong><code>src</code></strong> — <strong>the source IP that will appear on the packet</strong></li>
</ul>
<p>That third one carries more weight than people give it. On a multi-homed box, "which address do we come from" determines whether the far end's firewall accepts you. It's not a guess and it's not always the interface's primary address.</p>
<p>The table itself, when you need it:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> route</span></span>
<span class="line"></span></code></pre>
<pre data-lang-none><code>default via 192.0.2.1 dev eth0
192.0.2.0/24 dev eth0 proto kernel scope link src 192.0.2.2
</code></pre>
<p>Reading it: <strong>most specific prefix wins.</strong> A <code>/32</code> beats a <code>/24</code> beats <code>/0</code>. <code>default</code> is <code>0.0.0.0/0</code> — the fallback. <code>proto kernel</code> means the kernel added it automatically when the address was configured. <code>scope link</code> means the destination is directly reachable without a gateway.</p>
<h3 id="multiple-routing-tables">Multiple routing tables</h3>
<p>Linux doesn't have one routing table. It has many, and a rule list deciding which to consult.</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> rule</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> show</span></span>
<span class="line"></span></code></pre>
<pre data-lang-none><code>0:      from all lookup local
32766:  from all lookup main
32767:  from all lookup default
</code></pre>
<p>Lowest priority number is evaluated first. <code>main</code> is what <code>ip route</code> shows by default. To see another:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> route</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> show</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> table</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> local</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> route</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> show</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> table</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 100</span></span>
<span class="line"></span></code></pre>
<p>This matters on multi-homed hosts, VPN setups, and anything using policy routing — where the source address, mark, or interface picks a different table. If <code>ip route get</code> returns something that contradicts <code>ip route</code>, a rule sent it to another table. Check <code>ip rule show</code> before assuming the kernel is wrong.</p>
<h3 id="asymmetric-routing-and-rpfilter">Asymmetric routing and rp_filter</h3>
<p>The failure that produces no error anywhere.</p>
<p>A packet arrives on <code>eth1</code>. The kernel checks: if I wanted to <em>reply</em> to this source address, would I send it out <code>eth1</code>? If the answer is no, <strong>reverse path filtering</strong> may discard the packet. Silently.</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sysctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> net.ipv4.conf.all.rp_filter</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sysctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> net.ipv4.conf.default.rp_filter</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sysctl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -a</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> 2></span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/dev/null</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '\.rp_filter'</span></span>
<span class="line"></span></code></pre>
<p>Three values:</p>
<ul>
<li><strong><code>0</code></strong> — off</li>
<li><strong><code>1</code></strong> — strict. The reply must go back out the <em>same</em> interface.</li>
<li><strong><code>2</code></strong> — loose. Any route to that source is enough.</li>
</ul>
<p>Strict mode plus asymmetric routing equals silently dropped traffic. It's designed as an anti-spoofing measure and it does that job well; it also breaks legitimate multi-homed and policy-routed setups.</p>
<p>The counter that proves it:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nstat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -az</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> IPReversePathFilter</span></span>
<span class="line"></span></code></pre>
<p>If that number is climbing, rp_filter is eating your packets. <strong>This is one of the very few drops that shows up nowhere else</strong> — not in interface counters, not at the socket layer, not in firewall rule counters. Know the counter's name and you'll find it in a minute instead of a day.</p>
<p>Note that the effective setting is per-interface, and the <code>all</code> value interacts with the per-interface one. Check <code>net.ipv4.conf.&lt;iface&gt;.rp_filter</code> for the interface in question, and read the behaviour in the kernel's <code>Documentation/networking/ip-sysctl.rst</code> before changing it.</p>
<h2 id="arp-and-the-neighbour-table">ARP and the neighbour table</h2>
<p>Layer 2, which the series has skipped so far. When a host sends to an IP on its own subnet, it needs that host's MAC address. ARP is the discovery protocol; the results live in the neighbour table.</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> neigh</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> show</span></span>
<span class="line"></span></code></pre>
<pre data-lang-none><code>192.0.2.1 dev eth0 lladdr 02:fc:00:00:00:05 REACHABLE
</code></pre>
<p>The states tell a story:</p>
<table>
<thead>
<tr>
<th>State</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>REACHABLE</code></td>
<td>Confirmed recently. Healthy.</td>
</tr>
<tr>
<td><code>STALE</code></td>
<td>Known, but not confirmed lately. Normal — will be re-validated on use.</td>
</tr>
<tr>
<td><code>DELAY</code> / <code>PROBE</code></td>
<td>Mid-revalidation. Transient.</td>
</tr>
<tr>
<td><code>FAILED</code></td>
<td>Resolution attempted and failed.</td>
</tr>
<tr>
<td><code>INCOMPLETE</code></td>
<td><strong>ARP sent, no reply.</strong></td>
</tr>
</tbody>
</table>
<p><strong><code>INCOMPLETE</code> is the diagnostic.</strong> It means: I broadcast "who has this IP?" onto the local segment and nobody answered. The host is off, not on this segment, or something is filtering ARP.</p>
<p>That's a fundamentally different failure from "connection refused" or "connection timed out at the TCP layer," and it's <em>below</em> everything else in the series. No amount of <code>ss</code> or <code>nstat</code> will show it.</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> neigh</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> flush</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dev</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth0</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        # force re-resolution</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">arping</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -I</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth0</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 192.0.2.1</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">       # probe directly (package: iputils-arping or arping)</span></span>
<span class="line"></span></code></pre>
<h3 id="duplicate-ips">Duplicate IPs</h3>
<p>If two hosts claim the same address, ARP replies alternate and connectivity becomes maddeningly intermittent — works, then doesn't, then works. <code>arping</code> will show replies from two different MAC addresses for one IP. Once you've seen it, the symptom is unmistakable; before that it looks like haunted hardware.</p>
<h3 id="neighbour-table-overflow">Neighbour table overflow</h3>
<p>On large flat networks the table can fill:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sysctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> net.ipv4.neigh.default.gc_thresh1</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sysctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> net.ipv4.neigh.default.gc_thresh2</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sysctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> net.ipv4.neigh.default.gc_thresh3</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">dmesg</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'neighbour table overflow'</span></span>
<span class="line"></span></code></pre>
<p><code>gc_thresh3</code> is the hard maximum. Hit it and entries get evicted aggressively, causing intermittent failures to hosts that were fine a moment ago. The dmesg line is unambiguous when it happens. Common on hosts with thousands of neighbours — large L2 segments, or busy container hosts.</p>
<h2 id="reading-firewall-rules">Reading firewall rules</h2>
<p>I've said "check your firewall rules" three times in this series without showing you how. Here's how, and the technique matters more than the syntax.</p>
<h3 id="iptables">iptables</h3>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">iptables</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -L</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -v</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --line-numbers</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">iptables</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> nat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -L</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -v</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --line-numbers</span></span>
<span class="line"></span></code></pre>
<ul>
<li><code>-L</code> list, <code>-n</code> numeric (<strong>always</strong> — otherwise it resolves every address), <code>-v</code> verbose, <code>--line-numbers</code> for reference.</li>
<li><code>-t nat</code> shows the NAT table. Forgetting this table exists causes a lot of confusion, because your <code>filter</code> rules look fine while NAT is rewriting everything.</li>
</ul>
<p><strong><code>-v</code> is the whole point.</strong> It adds two columns at the front:</p>
<pre data-lang-none><code>Chain INPUT (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source       destination
 4521  312K ACCEPT     tcp  --  *      *       0.0.0.0/0    0.0.0.0/0    tcp dpt:22
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0    0.0.0.0/0    tcp dpt:443
  892 53400 DROP       all  --  *      *       0.0.0.0/0    0.0.0.0/0
</code></pre>
<p>Those <code>pkts</code> counters are per-rule hit counts, and they turn firewall debugging from reading into measuring.</p>
<p>Look at that output: the rule accepting 443 has <strong>zero hits</strong>. The DROP at the bottom has 892. Traffic to 443 isn't matching the accept rule — wrong interface, wrong protocol, or an earlier rule caught it. You didn't have to reason about it. The counters said so.</p>
<p><strong>The technique that makes this decisive:</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">iptables</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -Z</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">              # zero all counters</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># ... now reproduce the problem ...</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">iptables</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -L</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -v</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        # which rule moved?</span></span>
<span class="line"></span></code></pre>
<p>Zero, reproduce, look. <strong>Whichever counter incremented is the rule that handled your packet.</strong> This is the single most useful firewall debugging move there is and most people never learn it.</p>
<p>Also check the chain policy — the <code>(policy DROP)</code> in the chain header. A default-DROP chain with no matching accept rule discards silently, which produces exactly the SYN-with-no-reply pattern from part 4.</p>
<h3 id="nftables">nftables</h3>
<p>Modern distros use nftables, often with <code>iptables</code> as a compatibility shim over it. If <code>iptables -L</code> looks empty but traffic is clearly being filtered, look here:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nft</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> list</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ruleset</span></span>
<span class="line"></span></code></pre>
<p>nftables doesn't count by default — you add <code>counter</code> to a rule to get statistics. If the ruleset was written without counters, add them to the rules you're investigating, or use logging.</p>
<h3 id="logging-what-gets-dropped">Logging what gets dropped</h3>
<p>When counters aren't enough, make the kernel tell you. Both <code>iptables</code> (<code>LOG</code> target) and <code>nftables</code> (<code>log</code> statement) can log matched packets to the kernel log, where <code>dmesg</code> or <code>journalctl -k</code> picks them up.</p>
<p>This is precise but dangerous on a busy box — an unrated log rule can flood your logs and your disk in minutes. Always rate-limit it, always remove it afterwards. Exact syntax is in <code>man iptables-extensions</code> and the <a href="https://wiki.nftables.org/">nftables wiki</a>; it's worth reading properly rather than pasting.</p>
<h3 id="conntrack-revisited">conntrack, revisited</h3>
<p>Part 3 covered the table filling up. The other half is inspecting it:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">conntrack</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -L</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                 # current tracked connections</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">conntrack</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -L</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -p</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tcp</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --dport</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 443</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">conntrack</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -E</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                 # live event stream</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">conntrack</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -S</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                 # per-CPU statistics</span></span>
<span class="line"></span></code></pre>
<p><code>conntrack -E</code> streams connection events as they happen — NEW, UPDATE, DESTROY. When you need to see whether the kernel is even creating state for a connection, this shows you live.</p>
<p>The failure to look for: a connection idle longer than conntrack's timeout gets its state removed, and the next packet arrives with no matching entry. Depending on rules, it's dropped or reset. That's the "connections die after exactly N minutes idle" pattern from part 4, seen from the kernel's side.</p>
<h2 id="containers-and-network-namespaces">Containers and network namespaces</h2>
<p>The modern gap. If you run containers and you've ever captured on the host and seen nothing, this is why.</p>
<h3 id="the-concept">The concept</h3>
<p>A network namespace is an independent copy of the entire network stack — its own interfaces, routing table, ARP table, firewall rules, socket list, and counters. Containers each get one.</p>
<p>Consequences that surprise people:</p>
<ul>
<li><code>ss -lntp</code> on the host <strong>does not show sockets inside containers.</strong></li>
<li><code>tcpdump</code> on the host interface <strong>does not see container-internal traffic.</strong></li>
<li><code>iptables -L</code> on the host is not the container's rule set.</li>
<li>Every counter in part 3 is per-namespace.</li>
</ul>
<p>Everything in this series still works. You just have to run it <em>in the right namespace</em>.</p>
<h3 id="getting-into-a-namespace">Getting into a namespace</h3>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> netns</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> list</span></span>
<span class="line"></span></code></pre>
<p><strong>Frequently empty on Docker hosts, and that's not a bug.</strong> <code>ip netns</code> only lists namespaces registered under <code>/var/run/netns/</code>, and Docker doesn't register them there. The namespaces exist; this command just can't see them.</p>
<p>The universal method — enter the namespace of a running process:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Get the container's main PID</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> inspect</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '{{.State.Pid}}'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">containe</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">r</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Run any network command inside its namespace</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nsenter</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">pi</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">d</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -lntp</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nsenter</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">pi</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">d</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ip</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -br</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> addr</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nsenter</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">pi</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">d</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> route</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nsenter</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">pi</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">d</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tcpdump</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -nn</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth0</span></span>
<span class="line"></span></code></pre>
<p><strong><code>nsenter -t &lt;pid&gt; -n &lt;command&gt;</code> is the key that unlocks the whole series inside containers.</strong> <code>-n</code> means "enter the network namespace only." The command runs with the host's filesystem and your host tools — so you get <code>tcpdump</code>, <code>ss</code>, and <code>nstat</code> inside a container that has none of them installed. That last point is worth pausing on: you do not need to install debugging tools in your images.</p>
<p>For Kubernetes, get the PID via <code>crictl inspect</code> on the node; the <code>nsenter</code> step is identical.</p>
<h3 id="veth-pairs-%E2%80%94-matching-both-ends">veth pairs — matching both ends</h3>
<p>A container connects to the host through a <strong>veth pair</strong>: two virtual interfaces acting as a pipe. One end is <code>eth0</code> inside the container, the other sits on the host, usually attached to a bridge.</p>
<p>Inside a container, <code>ip link show</code> gives you something like:</p>
<pre data-lang-none><code>12: eth0@if13: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; ...
</code></pre>
<p>That <strong><code>@if13</code> is the interface index of the peer on the host.</strong> So:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Inside the namespace — note the @ifN</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nsenter</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">pi</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">d</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> link</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> show</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth0</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># On the host — find index 13</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> link</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '^13:'</span></span>
<span class="line"></span></code></pre>
<p>Now you know exactly which host-side interface carries that container's traffic, and you can <code>tcpdump</code> it directly.</p>
<p>That trick — reading the peer index off <code>eth0@ifN</code> — turns "which of these forty <code>veth</code> interfaces belongs to my container" from guesswork into one command. It's the sort of thing that's obvious once and invisible before.</p>
<h3 id="bridges">Bridges</h3>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -br</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> link</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> show</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> type</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> bridge</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">bridge</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> link</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">bridge</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> fdb</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> show</span></span>
<span class="line"></span></code></pre>
<p><code>bridge fdb show</code> is the forwarding database — which MAC addresses the bridge has learned on which ports. The layer 2 equivalent of a routing table.</p>
<h3 id="where-container-traffic-actually-gets-natd">Where container traffic actually gets NAT'd</h3>
<p>Docker publishes ports by inserting NAT rules. To see them:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">iptables</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> nat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -L</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -v</span></span>
<span class="line"></span></code></pre>
<p>You'll find <code>DOCKER</code> chains doing DNAT from the host port to the container's address. When a published port doesn't work, this is where to look — and the per-rule counters tell you whether packets are reaching the DNAT rule at all.</p>
<p>A rule of thumb worth internalising: <strong>on container hosts, check the <code>nat</code> table before the <code>filter</code> table.</strong> More container networking problems are NAT problems than firewall problems.</p>
<h2 id="unix-domain-sockets">Unix domain sockets</h2>
<p>Not networking in the wire sense, but they carry a lot of production traffic — nginx to php-fpm, most database local connections, container runtime APIs — and the series would have a hole without them.</p>
<p>A Unix domain socket is an endpoint identified by a <strong>filesystem path</strong>, not an IP and port. Communication never leaves the kernel: no IP, no TCP, no checksums, no packets.</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -xlp</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">          # listening unix sockets, with process</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -xp</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">           # all unix sockets</span></span>
<span class="line"></span></code></pre>
<p>The "address" column is a path like <code>/run/php/php8.2-fpm.sock</code>.</p>
<p>Three things to know:</p>
<p><strong>They don't appear in <code>tcpdump</code>.</strong> There are no packets. If nginx talks to php-fpm over a Unix socket, no amount of capturing will show you those requests. People lose real time to this.</p>
<p><strong>Permissions matter.</strong> They're files, subject to file permissions. <code>connect() to unix:/... failed (13: Permission denied)</code> in an nginx log means the socket file's ownership or mode doesn't allow the nginx worker user. Not a network problem at all.</p>
<p><strong>They're faster than loopback TCP</strong> — no protocol overhead — but only work on one machine. That's the tradeoff behind "should php-fpm listen on a socket or 127.0.0.1:9000."</p>
<h2 id="what-did-we-just-build">What did we just build?</h2>
<p>The layers the rest of the series assumed. When a packet arrives and vanishes, you can now find it:</p>
<table>
<thead>
<tr>
<th>Symptom</th>
<th>Check</th>
</tr>
</thead>
<tbody>
<tr>
<td>No route / wrong source IP</td>
<td><code>ip route get &lt;dst&gt;</code>, <code>ip rule show</code></td>
</tr>
<tr>
<td>Packet arrives, vanishes, no counters move</td>
<td><code>nstat -az | grep IPReversePathFilter</code></td>
</tr>
<tr>
<td>Can't reach a host on the local subnet</td>
<td><code>ip neigh show</code> — <code>INCOMPLETE</code>?</td>
</tr>
<tr>
<td>Intermittent, works-then-doesn't on one IP</td>
<td>Duplicate IP — <code>arping</code> for two MACs</td>
</tr>
<tr>
<td>Intermittent failures, large L2 network</td>
<td><code>dmesg | grep 'neighbour table overflow'</code></td>
</tr>
<tr>
<td>Firewall suspected</td>
<td><code>iptables -Z</code>, reproduce, <code>iptables -L -n -v</code></td>
</tr>
<tr>
<td><code>iptables -L</code> empty but filtering happens</td>
<td><code>nft list ruleset</code></td>
</tr>
<tr>
<td>Published container port doesn't work</td>
<td><code>iptables -t nat -L -n -v</code></td>
</tr>
<tr>
<td>Host tools show nothing for a container</td>
<td><code>nsenter -t &lt;pid&gt; -n &lt;command&gt;</code></td>
</tr>
<tr>
<td>Which veth belongs to this container</td>
<td><code>eth0@ifN</code> inside → index N on host</td>
</tr>
<tr>
<td>nginx→php-fpm invisible to tcpdump</td>
<td>Unix socket — <code>ss -xlp</code>, check permissions</td>
</tr>
</tbody>
</table>
<p>Three habits from this post, if nothing else:</p>
<ol>
<li><strong><code>ip route get</code> instead of reading the table.</strong> Ask the kernel what it will do.</li>
<li><strong>Zero, reproduce, look.</strong> <code>iptables -Z</code> then check which counter moved. Stop reasoning about rule order; measure it.</li>
<li><strong><code>nsenter -t &lt;pid&gt; -n</code>.</strong> Every tool in this series, inside any container, without installing anything.</li>
</ol>
<p>That's the series. The cheat sheet post collects all of it into one page, with a glossary and a learning path.</p>
<p>Primary sources: <code>man ip-route</code>, <code>man ip-rule</code>, <code>man ip-neighbour</code>, <code>man iptables</code>, <code>man nsenter</code>, <a href="https://wiki.nftables.org/">nftables wiki</a>, and <code>Documentation/networking/ip-sysctl.rst</code> in the kernel tree.</p>
<hr>
<p>Compiled by AI. Proofread by caffeine. ☕</p>]]></content:encoded>
  </item>
  <item>
    <title>Linux Networking Part 5: Latency, Queues, and a Real nginx Hunt</title>
    <link>https://justmeandlinux.com/net-05-latency-and-queues/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/net-05-latency-and-queues/</guid>
    <pubDate>Sat, 15 Aug 2026 11:00:00 GMT</pubDate>
    <category>Linux</category>
    <description>&quot;The site is slow.&quot; Three words, and almost no information. Slow where? Slow for whom? Slow by how much, compared to what? Latency work is mostly attribution — cutting the total time into pieces until one piece is obviously the problem. Do that first and the…</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<p>"The site is slow."</p>
<p>Three words, and almost no information. Slow where? Slow for whom? Slow by how much, compared to what?</p>
<p>Latency work is mostly <strong>attribution</strong> — cutting the total time into pieces until one piece is obviously the problem. Do that first and the fix is usually easy. Skip it and you'll spend a day tuning sysctls that were never involved.</p>
<p>Final part of the series. We'll build the attribution method, cover the queues where latency actually accumulates, and then work a full nginx investigation using everything from parts 1 through 4.</p>
<h2 id="latency-is-queueing">Latency is queueing</h2>
<p>Almost every unexplained delay in a networked system is something waiting in a queue.</p>
<p>Actual transmission time is physics and it's small — light through fibre crosses a continent in tens of milliseconds and that number never changes. When something takes 800ms that should take 20ms, that extra 780ms was spent <em>waiting</em>: in a NIC ring buffer, a qdisc, a socket buffer, an accept queue, a thread pool, a connection pool, a disk queue.</p>
<p>So the question is never "why is the network slow." It's <strong>"which queue is it sitting in?"</strong> Every queue we've met in this series is a candidate:</p>
<table>
<thead>
<tr>
<th>Queue</th>
<th>Symptom when it backs up</th>
<th>How you see it</th>
</tr>
</thead>
<tbody>
<tr>
<td>NIC ring buffer</td>
<td>Drops, not delay</td>
<td><code>ip -s link</code> overruns (part 3)</td>
</tr>
<tr>
<td>Softirq backlog</td>
<td>Drops, one CPU pegged</td>
<td><code>/proc/net/softnet_stat</code> (part 3)</td>
</tr>
<tr>
<td>qdisc (transmit)</td>
<td>Delay, then drops</td>
<td><code>tc -s qdisc show</code> (part 3)</td>
</tr>
<tr>
<td>Socket receive buffer</td>
<td>App is behind</td>
<td><code>ss</code> Recv-Q (part 2)</td>
</tr>
<tr>
<td>Socket send buffer</td>
<td>Network or peer is behind</td>
<td><code>ss</code> Send-Q (part 2)</td>
</tr>
<tr>
<td>Accept queue</td>
<td>Hangs on connect</td>
<td><code>ss -lnt</code> Recv-Q (part 2)</td>
</tr>
<tr>
<td>App thread/worker pool</td>
<td>Everything slow at once</td>
<td>App metrics</td>
</tr>
<tr>
<td>Downstream connection pool</td>
<td>Slow, correlated with load</td>
<td>App metrics</td>
</tr>
<tr>
<td>Disk I/O queue</td>
<td>Slow, correlated with writes</td>
<td><code>iostat -x</code></td>
</tr>
</tbody>
</table>
<p>Ten candidates. The job is elimination, and the fastest elimination tool is a timing breakdown.</p>
<h2 id="split-the-total-time-first">Split the total time first</h2>
<p>Before touching the server, measure from the client. <code>curl</code> will decompose a request for you, and this is the most useful thirty seconds in the whole investigation.</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -w</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  dns:          %{time_namelookup}s</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  tcp connect:  %{time_connect}s</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  tls:          %{time_appconnect}s</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  pre-transfer: %{time_pretransfer}s</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  first byte:   %{time_starttransfer}s</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  total:        %{time_total}s</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -o</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /dev/null</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> https://example.com/</span></span>
<span class="line"></span></code></pre>
<p>These are <strong>cumulative from the start of the request</strong>, so you subtract to get each phase:</p>
<ul>
<li><strong><code>time_namelookup</code></strong> — DNS. If this is 2 seconds, your problem is DNS and nothing in this post applies.</li>
<li><strong><code>time_connect</code> − <code>time_namelookup</code></strong> — the TCP handshake. <strong>This is one round trip.</strong> It's your cleanest measurement of raw network RTT to that server, and it involves zero application code.</li>
<li><strong><code>time_appconnect</code> − <code>time_connect</code></strong> — the TLS handshake. Typically one or two additional round trips.</li>
<li><strong><code>time_starttransfer</code> − <code>time_pretransfer</code></strong> — <strong>time to first byte: the server thinking.</strong> This is where application latency lives.</li>
<li><strong><code>time_total</code> − <code>time_starttransfer</code></strong> — transferring the body. Large here with a small body means throughput problems.</li>
</ul>
<p>The interpretation is mechanical:</p>
<ul>
<li><strong>Connect time is high, TTFB is low</strong> → the network path is slow. The server is fine. Go look at RTT, routing, and loss.</li>
<li><strong>Connect time is low, TTFB is high</strong> → the network is fine. The server is slow. Stop reading tcpdump.</li>
<li><strong>Connect time is <em>erratic</em></strong> — fast usually, occasionally terrible → queueing. Accept queue, or loss causing retransmitted SYNs.</li>
<li><strong>Everything is fine but users still complain</strong> → it's not this path. Different region, different client, or a specific endpoint.</li>
</ul>
<p>Run it in a loop to see distribution rather than one sample:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">in</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">seq</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 20</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">); </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">do</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -w</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '%{time_connect} %{time_starttransfer} %{time_total}\n'</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    -o</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /dev/null</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> https://example.com/</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">done</span></span>
<span class="line"></span></code></pre>
<p>Averages hide everything. What you're looking for is the shape: are all 20 slow, or are 18 fast and 2 catastrophic? Those are different problems. Consistent slowness is a capacity or path issue. Occasional catastrophic outliers are queueing or loss — a retransmission timeout, which is a delay of <em>seconds</em>, not milliseconds.</p>
<p>Full list of <code>curl</code> timing variables is under <code>-w</code> in <code>man curl</code>.</p>
<h2 id="path-latency-vs-application-latency">Path latency vs application latency</h2>
<p>If <code>curl</code> points at the network, narrow it further.</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ping</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -c</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 20</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 203.0.113.7</span></span>
<span class="line"></span></code></pre>
<p>Look at min/avg/max/mdev on the summary line. A tight spread means a stable path. A wide one means variable queueing somewhere along it.</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">mtr</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -rwc</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 100</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 203.0.113.7</span></span>
<span class="line"></span></code></pre>
<p><code>mtr</code> combines traceroute and ping — 100 probes to every hop, reporting loss and latency per hop. It's the right tool for "where along the path does it go bad."</p>
<p>One critical caveat that trips up almost everyone: <strong>loss reported at an intermediate hop is usually meaningless.</strong> Routers deprioritise generating ICMP replies for their own address; they'll happily show 40% "loss" while forwarding your actual traffic flawlessly. What matters is loss at the <strong>final hop</strong>, and loss that <strong>persists from a hop onward through all subsequent hops</strong>. Loss at hop 4 that disappears at hops 5 through 10 is an artifact. Loss that starts at hop 4 and continues to the end is real.</p>
<p>I've watched engineers escalate to a transit provider over hop-4 ICMP rate limiting more than once. Know this rule and you'll never be that person.</p>
<p>And remember the better measurement from part 2 — real RTT on the real connection carrying real traffic:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -tin</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> state</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> established</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dst</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 203.0.113.7</span></span>
<span class="line"></span></code></pre>
<p><code>rtt:</code> versus <code>minrtt:</code> is the tell. If <code>minrtt</code> is 2ms and current <code>rtt</code> is 180ms, the path <em>can</em> do 2ms and something is queueing right now. <code>ping</code> gives you a synthetic probe that may take a different path or priority; <code>ss -ti</code> gives you the truth about the connection you actually care about.</p>
<h2 id="throughput-window-over-rtt">Throughput: window over RTT</h2>
<p>One formula explains most "why is my transfer slow over a long distance" questions:</p>
<pre data-lang-none><code>max throughput ≈ window size / round-trip time
</code></pre>
<p>A 64KB window over a 100ms RTT caps you at roughly 5 Mbit/s — <strong>regardless of whether the link is 10Gbit.</strong> The sender fills the window, then waits a full round trip for acknowledgement before it can continue.</p>
<p>That product — bandwidth × RTT — is the bandwidth-delay product, and it's the amount of data that must be in flight to keep a link busy.</p>
<p>Linux autotunes receive buffers by default (<code>net.ipv4.tcp_moderate_rcvbuf</code>), and the tunables are <code>net.ipv4.tcp_rmem</code> and <code>net.ipv4.tcp_wmem</code> — each three values: minimum, default, maximum. <code>net.core.rmem_max</code> and <code>net.core.wmem_max</code> cap what an application can request explicitly.</p>
<p><strong>Do not go tuning these because a blog told you to.</strong> The defaults are sane for typical deployments, autotuning works, and badly-chosen values make things worse while consuming memory. This matters when you have genuinely high bandwidth-delay product paths — cross-continent bulk transfer, replication between distant regions. It does not matter for a web server serving clients 20ms away, and turning these knobs there is cargo cult. Read <code>man 7 tcp</code> before changing anything, and measure before and after.</p>
<p>If your traffic crosses long distances, the congestion control algorithm is worth a look:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sysctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> net.ipv4.tcp_available_congestion_control</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sysctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> net.ipv4.tcp_congestion_control</span></span>
<span class="line"></span></code></pre>
<p>CUBIC is the long-standing default. BBR takes a different approach — modelling bandwidth and RTT rather than treating loss as the congestion signal — and often performs considerably better on lossy long-distance paths. It's not universally better, and it's a change worth testing on your own traffic rather than adopting on faith.</p>
<h2 id="two-classic-latency-traps">Two classic latency traps</h2>
<h3 id="the-40ms-mystery">The 40ms mystery</h3>
<p>If you have request/response traffic that's consistently slow by a suspiciously round ~40ms, you may be hitting the interaction between <strong>Nagle's algorithm</strong> and <strong>delayed ACK</strong>.</p>
<p>Nagle's algorithm buffers small writes, waiting to coalesce them into a full segment or until outstanding data is acknowledged. Delayed ACK, on the other side, holds back acknowledgements briefly hoping to piggyback them on outgoing data.</p>
<p>Put them together and you can get a standoff: the sender waits for an ACK before sending its small segment, and the receiver waits for data before sending its ACK. The delayed-ACK timer eventually fires and breaks the deadlock — after a fixed delay.</p>
<p>The signature is that suspicious consistency. Real network problems are noisy; this is metronomic. The application-level fix is <code>TCP_NODELAY</code> (disabling Nagle), which most modern servers and libraries already set. If you're seeing this pattern, check whether your application sets it.</p>
<h3 id="ephemeral-port-exhaustion">Ephemeral port exhaustion</h3>
<p>The thing that looks like a network outage on a perfectly healthy box.</p>
<p>Every outbound connection needs a local source port from <code>net.ipv4.ip_local_port_range</code> — by default roughly 28,000 ports. And per part 2, closed connections hold their port in <code>TIME_WAIT</code> for 60 seconds.</p>
<p>So a service making outbound connections at a high rate, to a <strong>single destination IP and port</strong> — a database, a cache, an internal API — can run out. New connections fail. CPU is low, memory is fine, the network is healthy, and nothing works.</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -tan</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> state</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> time-wait</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> wc</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -l</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sysctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> net.ipv4.ip_local_port_range</span></span>
<span class="line"></span></code></pre>
<p>Thousands of <code>TIME_WAIT</code> entries all pointing at one destination is the fingerprint.</p>
<p>The tempting fixes are widening the port range and enabling <code>net.ipv4.tcp_tw_reuse</code>. Both help, both are treating symptoms, and one of them has caveats you should read in <code>tcp(7)</code> before deploying.</p>
<p><strong>The real fix is connection reuse.</strong> If your service opens a fresh connection for every request to the same backend, that's the bug. Connection pooling and HTTP keepalive eliminate the problem entirely instead of raising the ceiling on it. And as established in part 2: never <code>tcp_tw_recycle</code>. It's gone from the kernel and it broke NAT'd clients when it existed.</p>
<h2 id="disk-latency-wearing-a-network-costume">Disk latency wearing a network costume</h2>
<p>Worth saying plainly, because it's a common misdiagnosis: <strong>application latency backs up into the network stack.</strong></p>
<p>Your app is blocked on a slow disk read. It isn't calling <code>read()</code> on its sockets. Recv-Q climbs. It isn't calling <code>accept()</code>. The accept queue fills. Clients hang and time out.</p>
<p>Every symptom points at the network. The cause is a disk.</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">iostat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -x</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span></span>
<span class="line"></span></code></pre>
<p>The columns that matter: <code>r_await</code> and <code>w_await</code> — average time in milliseconds for read and write requests, <strong>including queue time</strong> — and <code>aqu-sz</code>, the average queue depth. Column names vary between <code>sysstat</code> versions; check the header your box prints.</p>
<p>A word on <code>%util</code>: on spinning disks it meant something. On SSDs and NVMe, which service many requests in parallel, it does not mean saturation. A device at 100% <code>%util</code> with 0.2ms await is completely fine. <strong>Use await, not %util.</strong> People still make capacity decisions on that number and they're making them wrong.</p>
<p>For deeper work, <code>biolatency</code> from the BCC tools gives you a latency histogram rather than an average — and averages hide the tail that your users are actually experiencing.</p>
<p>Check CPU too, and specifically softirq time:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">mpstat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -P</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ALL</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span></span>
<span class="line"></span></code></pre>
<p>Per-CPU breakdown. The <code>%soft</code> column is software interrupt time — network processing. One core at 100% <code>%soft</code> while the rest idle is the imbalance from part 3, and it causes both drops and latency.</p>
<h2 id="case-study-nginx-is-slow-and-sometimes-returns-502">Case study: nginx is slow and sometimes returns 502</h2>
<p>Now the whole series at once. Symptom: users report intermittent slowness, occasional 502 Bad Gateway. Load is up but not extreme. Nobody knows anything else.</p>
<h3 id="step-1-%E2%80%94-where-is-the-time-going">Step 1 — Where is the time going?</h3>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">in</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">seq</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 20</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">); </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">do</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -w</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '%{time_connect} %{time_starttransfer} %{time_total}\n'</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    -o</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /dev/null</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> https://oursite.example/</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">done</span></span>
<span class="line"></span></code></pre>
<p>Say the result is: 16 requests with connect ~0.02s and TTFB ~0.05s, and 4 requests with connect ~1.02s.</p>
<p>Read that carefully. It isn't uniform slowness. Connect time is <em>occasionally</em> about a second — and 1 second is exactly the first SYN retransmission interval from part 4.</p>
<p><strong>Hypothesis: some connections aren't getting through on the first SYN.</strong> Not application slowness. Something at connection setup.</p>
<h3 id="step-2-%E2%80%94-is-the-listener-keeping-up">Step 2 — Is the listener keeping up?</h3>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -lnt</span></span>
<span class="line"></span></code></pre>
<pre data-lang-none><code>State   Recv-Q  Send-Q  Local Address:Port
LISTEN  487     511     0.0.0.0:443
</code></pre>
<p>487 of 511. The accept queue is nearly full. Confirm it's overflowing:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nstat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -az</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> listen</span></span>
<span class="line"></span></code></pre>
<p><code>TcpExtListenOverflows</code> is climbing.</p>
<p><strong>Confirmed.</strong> Handshakes complete, connections sit in the accept queue, nginx isn't picking them up fast enough, and the queue overflows. Dropped final ACKs mean SYN-ACK retransmissions and that ~1 second penalty. Exactly the part-4 pattern.</p>
<p>Note what we have <em>not</em> done: no tcpdump, no sysctl tuning. Two commands.</p>
<h3 id="step-3-%E2%80%94-rule-out-the-layers-below">Step 3 — Rule out the layers below</h3>
<p>Be rigorous. Prove there's nothing else:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> link</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> show</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth0</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">             # errors, overruns — clean</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">cat</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /proc/net/softnet_stat</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">       # column 2 zero, column 1 even across CPUs</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nstat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -az</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> retrans</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">      # not moving</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">conntrack</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -S</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                     # no drops (or n/a on this box)</span></span>
<span class="line"></span></code></pre>
<p>All clean. The wire is fine, softirq is balanced, the path isn't lossy, conntrack isn't full. <strong>The network is exonerated</strong>, and now you can say so with evidence rather than opinion.</p>
<h3 id="step-4-%E2%80%94-why-isnt-nginx-accepting">Step 4 — Why isn't nginx accepting?</h3>
<p>The accept queue is a symptom. Raising <code>somaxconn</code> and the <code>backlog</code> would give it a deeper queue to fall behind in. Find the actual cause.</p>
<p>Check the error log first, always:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tail</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -100</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /var/log/nginx/error.log</span></span>
<span class="line"></span></code></pre>
<p>Three messages nginx produces that answer this question immediately:</p>
<ul>
<li><strong><code>worker_connections are not enough</code></strong> — you've hit the <code>worker_connections</code> limit. Every connection nginx handles, client-facing <em>and</em> upstream, consumes one.</li>
<li><strong><code>accept4() failed (24: Too many open files)</code></strong> — file descriptor limit. Check <code>worker_rlimit_nofile</code> in nginx and the process limit (<code>LimitNOFILE</code> under systemd; <code>cat /proc/&lt;pid&gt;/limits</code> shows what the running process actually has, which is the number that counts).</li>
<li><strong><code>upstream timed out</code></strong> — nginx is waiting on a backend. Workers tied up waiting can't accept new connections.</li>
</ul>
<p>That third one is the common answer, and it's a nice reminder that "nginx is slow" is very often "the thing behind nginx is slow."</p>
<h3 id="step-5-%E2%80%94-split-nginx-time-from-upstream-time">Step 5 — Split nginx time from upstream time</h3>
<p>nginx exposes exactly the variables you need in <code>log_format</code>:</p>
<ul>
<li><code>$request_time</code> — total time nginx spent on the request.</li>
<li><code>$upstream_connect_time</code> — time to establish the upstream connection.</li>
<li><code>$upstream_header_time</code> — time until upstream response headers arrived.</li>
<li><code>$upstream_response_time</code> — total upstream time.</li>
</ul>
<p>If they aren't in your access log format, add them. Then:</p>
<ul>
<li><strong><code>$upstream_response_time</code> ≈ <code>$request_time</code></strong> → nginx is a passenger. The backend is slow. Go there.</li>
<li><strong><code>$request_time</code> ≫ <code>$upstream_response_time</code></strong> → nginx or the client link is the delay. Slow clients, TLS overhead, or nginx resource limits.</li>
<li><strong><code>$upstream_connect_time</code> high</strong> → connecting to the backend is slow. Backend accept queue full, or backend port exhaustion. <strong>Run this same investigation one layer down.</strong></li>
</ul>
<p>That recursion is the point. The method doesn't change; you just move a layer.</p>
<p>Exact variable semantics are in <a href="https://nginx.org/en/docs/http/ngx_http_log_module.html">the nginx log module docs</a> and <a href="https://nginx.org/en/docs/http/ngx_http_upstream_module.html">the upstream module docs</a>. Read them there, not here.</p>
<h3 id="step-6-%E2%80%94-check-upstream-connection-reuse">Step 6 — Check upstream connection reuse</h3>
<p>If <code>$upstream_connect_time</code> is high and the backend looks healthy, check whether nginx is opening a new connection per request to it. By default in a <code>proxy_pass</code> setup, it does.</p>
<p>The <code>keepalive</code> directive in an <code>upstream</code> block enables a pool of persistent connections. Making it actually work also requires <code>proxy_http_version 1.1</code> and clearing the <code>Connection</code> header — the exact configuration is documented in the upstream module docs linked above, and it's worth reading rather than pasting.</p>
<p>Without it, a high-traffic proxy burns through source ports and produces exactly the <code>TIME_WAIT</code> pile-up described earlier. Check:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -tan</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> state</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> time-wait</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> awk</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '{print $5}'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> sort</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> uniq</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -c</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> sort</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -rn</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> head</span></span>
<span class="line"></span></code></pre>
<p>If one backend dominates that list, connection reuse is your fix.</p>
<h3 id="step-7-%E2%80%94-the-fix-in-order">Step 7 — The fix, in order</h3>
<ol>
<li><strong>Fix the actual cause</strong> — the slow upstream, the FD limit, the exhausted worker connections. This is the fix.</li>
<li><strong>Enable upstream keepalive</strong> if nginx is proxying at volume.</li>
<li><strong>Then, and only then</strong>, consider raising the accept queue — <code>net.core.somaxconn</code> and the <code>backlog</code> parameter on <code>listen</code>, together, since the kernel takes the minimum. A deeper queue absorbs bursts. It does not create capacity.</li>
</ol>
<p>Order matters. Do 3 first and you've hidden the symptom and moved the failure to a worse place — instead of failing fast, connections now wait a long time and <em>then</em> fail.</p>
<h3 id="what-that-investigation-actually-cost">What that investigation actually cost</h3>
<p>Six commands and a log file. No packet capture, no kernel tuning, no guessing.</p>
<p>The path was: <strong>measure from outside → localise to a layer → prove the layers below are clean → find the cause → fix the cause, not the symptom.</strong></p>
<p>That's the method. The commands are interchangeable. The method isn't.</p>
<h2 id="the-umbrella">The umbrella</h2>
<p>Since this is the last part, here's the whole thing compressed.</p>
<p><strong>Everything is a queue.</strong> Networking is producers, consumers, and buffers between them. Every problem is either "a queue is full" (drops) or "a queue is deep" (latency). Find the queue.</p>
<p><strong>Every tool reads one layer.</strong> <code>ethtool</code> and <code>ip -s link</code> read the wire. <code>/proc/net/softnet_stat</code> reads packet processing. <code>nstat</code> reads the IP and TCP stack. <code>ss</code> reads sockets and the application boundary. <code>tcpdump</code> reads what's actually on the wire. Knowing which layer a tool reads is knowing when to run it.</p>
<p><strong>Drops and retransmits are different evidence.</strong> Drop counters mean <em>this box</em> discarded something. Retransmits mean <em>the path</em> lost something. They point in opposite directions.</p>
<p><strong>Localise before you tune.</strong> Every sysctl in every tuning guide is a guess until you know which layer is failing. Measure, localise, then change one thing and measure again.</p>
<p><strong>The application is part of the network stack.</strong> Recv-Q, accept queues, <code>CLOSE_WAIT</code>, port exhaustion — every one of these is caused by application behaviour and diagnosed with network tools. "The network is slow" is, more often than not, "our code stopped reading its sockets."</p>
<h2 id="where-to-go-next">Where to go next</h2>
<p>Two directions, both worth it.</p>
<p><strong>Down, into eBPF.</strong> <code>bpftrace</code> and the BCC tools let you ask questions counters can't answer: which connection retransmitted, how long each socket lived, exactly where in the kernel a packet was dropped. <code>tcpretrans</code>, <code>tcplife</code>, <code>tcpconnect</code>, <code>biolatency</code>. This is the current frontier of Linux observability and it's the clearest line between competent and genuinely strong. Start at <a href="https://github.com/iovisor/bcc">the BCC repo</a> and <a href="https://github.com/bpftrace/bpftrace">bpftrace</a>.</p>
<p><strong>Sideways, into the protocol.</strong> Read <em>TCP/IP Illustrated, Volume 1</em> (Stevens; the second edition is updated by Fall). Nothing else builds the same depth. Then read <code>man 7 tcp</code> end to end — it is dense, unglamorous, and better than most books.</p>
<p>And the unfashionable advice: <strong>capture traffic for things that are working.</strong> Take a tcpdump of a healthy request. Watch a normal handshake. Read <code>ss -ti</code> on a connection that's fine. You cannot recognise broken until you know exactly what healthy looks like, and 3am is a terrible time to be learning it.</p>
<p>Next post is the cheat sheet — every command in this series in one page, plus a learning path.</p>
<hr>
<p>Compiled by AI. Proofread by caffeine. ☕</p>]]></content:encoded>
  </item>
  <item>
    <title>Linux Networking Part 4: Reading tcpdump</title>
    <link>https://justmeandlinux.com/net-04-reading-tcpdump/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/net-04-reading-tcpdump/</guid>
    <pubDate>Sat, 15 Aug 2026 10:00:00 GMT</pubDate>
    <category>Linux</category>
    <description>Counters tell you that something is wrong. tcpdump tells you what happened . Most engineers can start a capture. Far fewer can look at forty lines of output and say &quot;the firewall is dropping this&quot; or &quot;the server&apos;s accept queue is full&quot; without opening…</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<p>Counters tell you <em>that</em> something is wrong. <code>tcpdump</code> tells you <em>what happened</em>.</p>
<p>Most engineers can start a capture. Far fewer can look at forty lines of output and say "the firewall is dropping this" or "the server's accept queue is full" without opening Wireshark. That skill is smaller than it looks. There are maybe eight patterns worth recognising, and once you know them, most captures answer themselves in ten seconds.</p>
<p>Part 4 of the series. This one is about the wire.</p>
<h2 id="where-tcpdump-sits-and-why-it-matters">Where tcpdump sits, and why it matters</h2>
<p><code>tcpdump</code> taps traffic via <code>AF_PACKET</code>, close to the driver. The consequence is worth learning cold:</p>
<p><strong>On receive, tcpdump sees packets before netfilter.</strong> So if <code>iptables</code> or <code>nftables</code> is going to drop that packet, tcpdump still shows it arriving.</p>
<p>That gives you the cleanest diagnostic split in Linux networking:</p>
<ul>
<li><strong>tcpdump shows the SYN arriving, but your application never sees the connection</strong> → the packet reached the box and something in the kernel ate it. Firewall rules, conntrack, or the socket layer.</li>
<li><strong>tcpdump shows nothing at all</strong> → the packet never got here. Routing, upstream firewall, wrong interface, the client isn't actually sending.</li>
</ul>
<p>One capture, and you've cut the problem in half.</p>
<h2 id="the-invocation">The invocation</h2>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tcpdump</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth0</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -nn</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> port</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 443</span></span>
<span class="line"></span></code></pre>
<p>The flags that matter:</p>
<pre data-lang-none><code>-i eth0     interface. -i any captures on all of them.
-nn         no DNS resolution, no port-name resolution. ALWAYS use this.
-c 100      stop after 100 packets.
-w file.pcap   write raw packets to a file.
-r file.pcap   read a file back.
-v / -vv    more detail (TTL, IP ID, checksum validation).
-e          show the ethernet header — MAC addresses and VLAN tags.
-A          print payload as ASCII.
-X          print payload as hex and ASCII.
-S          absolute sequence numbers instead of relative.
-tttt       human-readable absolute timestamps.
</code></pre>
<p>Three habits worth building:</p>
<p><strong>Always <code>-nn</code>.</strong> Single <code>-n</code> skips host resolution; double skips port-name resolution too. Without it, tcpdump does a DNS lookup per address and your capture output stalls — during a network incident, possibly forever. It also renders port 443 as "https", which is noise when you're pattern-matching.</p>
<p><strong>Always bound the capture.</strong> <code>-c 500</code>, or <code>-w</code> to a file with a size limit. An unbounded capture on a busy interface fills a terminal faster than you can read and can fill a disk.</p>
<p><strong>For anything non-trivial, capture to a file.</strong> Read it on the box with <code>tcpdump -r</code>, or pull it down and open it in Wireshark. Live terminal output is for quick yes/no questions.</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># The pattern for real work</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tcpdump</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth0</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -nn</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -w</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /tmp/capture.pcap</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'host 203.0.113.7 and port 443'</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># ... reproduce the problem ...</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Ctrl-C</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tcpdump</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -r</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /tmp/capture.pcap</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -nn</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> head</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -50</span></span>
<span class="line"></span></code></pre>
<p>On modern versions the default snapshot length already captures full packets, so the old <code>-s 0</code> habit is unnecessary. Harmless, just dated.</p>
<h3 id="two-gotchas-that-will-confuse-you">Two gotchas that will confuse you</h3>
<p><strong><code>-i any</code> doesn't give you an ethernet header.</strong> It uses a special cooked-capture mode. If you need MAC addresses or VLAN tags, capture on the real interface.</p>
<p><strong>Offloads make packets look impossible.</strong> As covered in part 3: GRO merges incoming segments before tcpdump sees them, TSO means outgoing segments are split after. You will see a 40,000-byte "packet" on a 1500-byte MTU link. Nothing is wrong. You're seeing a merged buffer, not a frame that existed on the wire. Remember this before you spend an hour on it.</p>
<h2 id="capture-filters">Capture filters</h2>
<p>The filter syntax is BPF, and it's the same language <code>libpcap</code> uses everywhere. It's worth learning properly because it's the difference between 40 relevant packets and 40,000 irrelevant ones.</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># By host</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tcpdump</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -nn</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> host</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 203.0.113.7</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tcpdump</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -nn</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> src</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> host</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 203.0.113.7</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tcpdump</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -nn</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dst</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> host</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 203.0.113.7</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># By port</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tcpdump</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -nn</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> port</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 443</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tcpdump</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -nn</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dst</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> port</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 443</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tcpdump</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -nn</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> portrange</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 8000-8100</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># By network</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tcpdump</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -nn</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> net</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 10.0.0.0/8</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># By protocol</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tcpdump</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -nn</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tcp</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tcpdump</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -nn</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> udp</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> port</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 53</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tcpdump</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -nn</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> icmp</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Combined — quote it so the shell leaves it alone</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tcpdump</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -nn</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'host 203.0.113.7 and port 443 and tcp'</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tcpdump</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -nn</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'port 80 or port 443'</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tcpdump</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -nn</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'host 203.0.113.7 and not port 22'</span></span>
<span class="line"></span></code></pre>
<p>That last one — excluding your own SSH session — is one you'll use every single time you capture on a box you're logged into. Otherwise you're watching yourself watch the traffic.</p>
<h3 id="filtering-on-tcp-flags">Filtering on TCP flags</h3>
<p>This is the power move, and it's how you answer real questions in one line:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Only SYNs — who is trying to connect to us?</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tcpdump</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -nn</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'tcp[tcpflags] &#x26; tcp-syn != 0'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Only RSTs — who is getting rejected?</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tcpdump</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -nn</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'tcp[tcpflags] &#x26; tcp-rst != 0'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Connection setup and teardown only, no data</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tcpdump</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -nn</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'tcp[tcpflags] &#x26; (tcp-syn|tcp-fin|tcp-rst) != 0'</span></span>
<span class="line"></span></code></pre>
<p>The named constants are <code>tcp-syn</code>, <code>tcp-ack</code>, <code>tcp-fin</code>, <code>tcp-rst</code>, <code>tcp-push</code>, <code>tcp-urg</code>. Full syntax is in <code>man pcap-filter</code>, which is genuinely one of the better man pages on the system.</p>
<p>That middle command deserves its own callout. <strong>"Show me every reset on this box"</strong> is often the fastest route to a root cause, because RSTs are where things go wrong and someone is usually being explicit about it.</p>
<h2 id="reading-a-line">Reading a line</h2>
<p>Here's a full handshake. This is the thing to be able to read at a glance:</p>
<pre data-lang-none><code>14:23:45.123456 IP 203.0.113.7.51234 &gt; 10.0.0.5.443: Flags [S], seq 1000, win 64240, options [mss 1460,sackOK,TS val 111 ecr 0,nop,wscale 7], length 0
14:23:45.123789 IP 10.0.0.5.443 &gt; 203.0.113.7.51234: Flags [S.], seq 5000, ack 1001, win 65160, options [mss 1460,sackOK,TS val 222 ecr 111,nop,wscale 7], length 0
14:23:45.145123 IP 203.0.113.7.51234 &gt; 10.0.0.5.443: Flags [.], ack 5001, win 502, length 0
</code></pre>
<p>Field by field:</p>
<ul>
<li><strong><code>14:23:45.123456</code></strong> — timestamp, microsecond resolution. <strong>The deltas between lines are your latency measurement.</strong> In this capture, SYN to SYN-ACK is 333 microseconds — the server responded instantly. Client ACK comes 21ms later, which is the network round trip. Right there you've measured RTT without running <code>ping</code>.</li>
<li><strong><code>IP</code></strong> — IPv4. You'll see <code>IP6</code> for v6.</li>
<li><strong><code>203.0.113.7.51234 &gt; 10.0.0.5.443</code></strong> — source, then destination. Note tcpdump uses a <strong>dot</strong> before the port, not a colon. It reads oddly at first, then never again.</li>
<li><strong><code>Flags [S]</code></strong> — the TCP flags. The whole game. Decoded below.</li>
<li><strong><code>seq 1000</code></strong> — sequence number. Relative to the connection start by default, which is what you want. Use <code>-S</code> for absolute.</li>
<li><strong><code>ack 1001</code></strong> — the next byte expected from the peer.</li>
<li><strong><code>win 64240</code></strong> — the receive window. How much the sender of <em>this</em> packet is willing to accept.</li>
<li><strong><code>options [...]</code></strong> — negotiated at handshake. <code>mss</code> is max segment size, <code>sackOK</code> means selective ACK is supported, <code>wscale 7</code> is the window scale factor.</li>
<li><strong><code>length 0</code></strong> — payload bytes. Zero for pure control packets.</li>
</ul>
<h3 id="the-flag-notation">The flag notation</h3>
<p>This trips up everyone once. Decode it once, properly:</p>
<table>
<thead>
<tr>
<th>Notation</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>[S]</code></td>
<td>SYN — opening a connection</td>
</tr>
<tr>
<td><code>[S.]</code></td>
<td>SYN+ACK — server accepting</td>
</tr>
<tr>
<td><code>[.]</code></td>
<td>ACK only — a bare acknowledgement</td>
</tr>
<tr>
<td><code>[P.]</code></td>
<td>PSH+ACK — data, push to the app</td>
</tr>
<tr>
<td><code>[F.]</code></td>
<td>FIN+ACK — graceful close</td>
</tr>
<tr>
<td><code>[R]</code></td>
<td>RST — abrupt reset</td>
</tr>
<tr>
<td><code>[R.]</code></td>
<td>RST+ACK — reset with acknowledgement</td>
</tr>
</tbody>
</table>
<p><strong>The dot is the ACK flag.</strong> That's the whole trick. <code>[S]</code> is a bare SYN, <code>[S.]</code> is SYN with ACK set. Once you see the dot as "ACK," the notation is obvious forever.</p>
<h3 id="the-window-scale-trap">The window scale trap</h3>
<p><code>win 502</code> on that third line looks alarmingly small. It isn't. The handshake negotiated <code>wscale 7</code>, so the real window is 502 × 2⁷ = 64,256 bytes.</p>
<p>The trap: <strong>if you start a capture on a connection that's already established, tcpdump never saw the handshake, so it can't apply the scale factor.</strong> Every window value it prints will be wrong by a large multiple. If window sizes look absurd, check whether you captured the handshake. Whenever you suspect a windowing problem, restart the capture and reproduce the connection from scratch.</p>
<h2 id="the-patterns">The patterns</h2>
<p>This is the part to keep. Eight signatures, each with a diagnosis.</p>
<h3 id="1-syn-with-no-response-%E2%80%94-a-firewall-is-dropping">1. SYN with no response — a firewall is dropping</h3>
<pre data-lang-none><code>14:23:45.100 IP 203.0.113.7.51234 &gt; 10.0.0.5.443: Flags [S], seq 1000, length 0
14:23:46.100 IP 203.0.113.7.51234 &gt; 10.0.0.5.443: Flags [S], seq 1000, length 0
14:23:48.100 IP 203.0.113.7.51234 &gt; 10.0.0.5.443: Flags [S], seq 1000, length 0
14:23:52.100 IP 203.0.113.7.51234 &gt; 10.0.0.5.443: Flags [S], seq 1000, length 0
</code></pre>
<p>Same sequence number, retransmitted, roughly doubling intervals — 1s, 2s, 4s. That's TCP's exponential backoff.</p>
<p><strong>Diagnosis: something is silently discarding the packet.</strong> A <code>DROP</code> firewall rule, a security group, a network ACL. Silence is the signature of DROP; a REJECT would send something back.</p>
<p>Client-side symptom: a long hang, then "connection timed out."</p>
<p>Now combine it with where you captured:</p>
<ul>
<li>Ran this <strong>on the server</strong> and you see the SYNs arriving but no SYN-ACK leaving? The packet reached the box. A <strong>local</strong> firewall rule is eating it. Check <code>iptables -L -n -v</code> / <code>nft list ruleset</code> and look at the counters — the rule that's dropping will have a rising packet count.</li>
<li>Ran it on the server and you see <strong>nothing</strong>? The packet never arrived. Upstream: routing, cloud security group, network firewall.</li>
</ul>
<p>Two captures, problem localised.</p>
<h3 id="2-syn-then-rst-%E2%80%94-nothing-is-listening">2. SYN then RST — nothing is listening</h3>
<pre data-lang-none><code>14:23:45.100 IP 203.0.113.7.51234 &gt; 10.0.0.5.443: Flags [S], seq 1000, length 0
14:23:45.100 IP 10.0.0.5.443 &gt; 203.0.113.7.51234: Flags [R.], seq 1, ack 1001, length 0
</code></pre>
<p>Immediate reset. <strong>Diagnosis: no socket is listening on that port</strong>, or a firewall configured to REJECT rather than DROP.</p>
<p>Client symptom: instant "connection refused."</p>
<p>The speed is the tell. Refused is <em>fast</em>; dropped is <em>slow</em>. If a user says "it fails immediately," you already know it's this class of problem before you capture anything. Confirm with <code>ss -lnt</code> from part 2 — and check the bind address, because a service bound to <code>127.0.0.1</code> produces exactly this from outside.</p>
<h3 id="3-handshake-completes-then-repeated-syn-ack-%E2%80%94-the-accept-queue-is-full">3. Handshake completes, then repeated SYN-ACK — the accept queue is full</h3>
<pre data-lang-none><code>14:23:45.100 IP client.51234 &gt; server.443: Flags [S], seq 1000, length 0
14:23:45.101 IP server.443 &gt; client.51234: Flags [S.], seq 5000, ack 1001, length 0
14:23:45.120 IP client.51234 &gt; server.443: Flags [.], ack 5001, length 0
14:23:46.101 IP server.443 &gt; client.51234: Flags [S.], seq 5000, ack 1001, length 0
14:23:48.101 IP server.443 &gt; client.51234: Flags [S.], seq 5000, ack 1001, length 0
</code></pre>
<p>The client completed the handshake. The server keeps retransmitting its SYN-ACK as if it never got the final ACK.</p>
<p><strong>Diagnosis: the server's accept queue was full when that ACK arrived.</strong> With <code>net.ipv4.tcp_abort_on_overflow = 0</code> — the default — the kernel discards the ACK rather than resetting, and retransmits the SYN-ACK hoping the application catches up.</p>
<p>This is the packet-level signature of the problem from part 2. Confirm it in two seconds:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -lnt</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                            # Recv-Q at Send-Q on that listener</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nstat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -az</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ListenOverflow</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> # rising</span></span>
<span class="line"></span></code></pre>
<p>The reason this matters: from the client's point of view it looks <em>exactly</em> like network loss. Same hang, same retransmissions. Teams have blamed their network provider for weeks over an application that was too slow to <code>accept()</code>.</p>
<h3 id="4-duplicate-acks-and-retransmission-%E2%80%94-genuine-loss">4. Duplicate ACKs and retransmission — genuine loss</h3>
<pre data-lang-none><code>IP server &gt; client: Flags [P.], seq 1000:2000, length 1000
IP server &gt; client: Flags [P.], seq 2000:3000, length 1000
IP client &gt; server: Flags [.], ack 2000, length 0
IP client &gt; server: Flags [.], ack 2000, length 0
IP client &gt; server: Flags [.], ack 2000, length 0
IP server &gt; client: Flags [P.], seq 2000:3000, length 1000
</code></pre>
<p>The client keeps acking 2000 — it's saying "I'm still missing the byte at 2000." After the third duplicate ACK the server fast-retransmits.</p>
<p><strong>Diagnosis: real packet loss on the path.</strong> This is the pattern that justifies a call to the network team, and combined with a rising <code>TcpRetransSegs</code> from part 3, it's a solid case.</p>
<p>Note who's retransmitting. Loss on the path from server to client shows up as the <em>server</em> retransmitting.</p>
<h3 id="5-zero-window-%E2%80%94-the-receiver-is-drowning">5. Zero window — the receiver is drowning</h3>
<pre data-lang-none><code>IP client &gt; server: Flags [.], ack 50000, win 0, length 0
</code></pre>
<p><code>win 0</code> means "stop sending, my buffer is full." The sender must wait and periodically probe.</p>
<p><strong>Diagnosis: the receiving application isn't reading its socket fast enough.</strong> Same underlying condition as a climbing Recv-Q in part 2, seen from the other side.</p>
<p>Users experience this as a transfer that stalls, resumes, stalls. It is <em>not</em> a network problem — the network delivered everything perfectly. The receiver couldn't cope.</p>
<h3 id="6-rst-mid-connection-%E2%80%94-someone-hung-up">6. RST mid-connection — someone hung up</h3>
<pre data-lang-none><code>IP client &gt; server: Flags [P.], seq 1000:1500, length 500
IP server &gt; client: Flags [R.], seq 5000, ack 1500, length 0
</code></pre>
<p>An established connection, working fine, suddenly reset.</p>
<p>Common causes, roughly in order of frequency:</p>
<ul>
<li>The application crashed or the process was killed.</li>
<li>A load balancer or proxy hit an idle timeout and tore the connection down.</li>
<li>A firewall or NAT device dropped the connection from its state table — often because the connection was idle longer than the device's timeout. Then the next packet has no matching state and gets reset.</li>
<li>The application deliberately reset, e.g. closing a socket with unread data in its receive buffer.</li>
</ul>
<p><strong>The idle-timeout case is the one to look for</strong>, because it has a fingerprint: connections die after a suspiciously round interval of inactivity. If everything breaks after exactly 350 seconds or exactly 5 minutes of idle, that's a middlebox timeout, not a bug in your code. TCP keepalives on a shorter interval than the middlebox timeout are the standard fix.</p>
<h3 id="7-fin-vs-rst-%E2%80%94-how-it-ended">7. FIN vs RST — how it ended</h3>
<pre data-lang-none><code># Graceful
IP client &gt; server: Flags [F.], seq 2000, ack 5000, length 0
IP server &gt; client: Flags [.], ack 2001, length 0
IP server &gt; client: Flags [F.], seq 5000, ack 2001, length 0
IP client &gt; server: Flags [.], ack 5001, length 0
</code></pre>
<p>Four packets: FIN, ACK, FIN, ACK. Both sides closed properly, all data delivered. This is what healthy looks like.</p>
<p>An RST instead means abrupt termination, and any data still in flight is discarded. Some RSTs are perfectly normal — plenty of software resets deliberately to avoid <code>TIME_WAIT</code>. A <em>change</em> in the FIN-to-RST ratio is the interesting signal, not the presence of resets.</p>
<h3 id="8-small-packets-fine-large-packets-stall-%E2%80%94-mtu-blackhole">8. Small packets fine, large packets stall — MTU blackhole</h3>
<p>The signature: the TCP handshake completes instantly, small requests work, and any transfer over a few kilobytes hangs and retransmits forever.</p>
<p><strong>Diagnosis: an MTU problem, with ICMP blocked.</strong></p>
<p>Here's the mechanism, because this one is genuinely subtle. When a packet is too large for a link and has the "don't fragment" bit set, the router is supposed to send back an ICMP "fragmentation needed" message telling the sender the correct MTU. Path MTU Discovery depends on it.</p>
<p>Some network administrator, at some point, decided to "block ICMP" for security. So that message never arrives. The sender has no idea its packets are too big, keeps sending them at full size, and they keep vanishing. Small packets fit and get through, which is why the handshake works and everything looks superficially fine.</p>
<p>Confirming it:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Send a large packet with DF set. Syntax varies between ping implementations.</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ping</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -M</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> do</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1472</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 203.0.113.7</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># The path MTU the kernel currently believes in</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> route</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> get</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 203.0.113.7</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Trace where the MTU changes</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tracepath</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 203.0.113.7</span></span>
<span class="line"></span></code></pre>
<p>1472 bytes of payload plus 28 bytes of headers is exactly 1500. If that fails but <code>-s 1400</code> succeeds, you have your answer. <code>tracepath</code> is specifically built to find where along the path the MTU drops.</p>
<p>This is endemic with VPNs, tunnels, and overlay networks, all of which reduce the effective MTU. And "we blocked ICMP for security" is the cause often enough that it's worth checking early. Check <code>ping</code> and <code>tracepath</code> syntax on your distro — flags differ between the <code>iputils</code> and BSD variants.</p>
<h2 id="a-workflow-not-a-command">A workflow, not a command</h2>
<p>When someone hands you "the connection doesn't work":</p>
<p><strong>Capture on the server, filtered to the client:</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tcpdump</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> any</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -nn</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -c</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 100</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'host &#x3C;client-ip>'</span></span>
<span class="line"></span></code></pre>
<p>Then read it against three questions:</p>
<ol>
<li><strong>Do you see the SYN?</strong> No → the packet never arrived. Look upstream: routing, cloud security groups, network firewalls. Stop capturing on this box.</li>
<li><strong>Do you see a response?</strong> No → the packet arrived and something local ate it. Check local firewall rule counters and conntrack.</li>
<li><strong>Does the handshake complete?</strong> Look at what happens next — repeated SYN-ACKs mean accept queue, an immediate RST means nothing listening, dup ACKs mean loss.</li>
</ol>
<p>If you can, <strong>capture on both ends simultaneously.</strong> A packet that leaves the client and never appears on the server has been dropped in between, and you've proven it. That comparison is the single most powerful thing in this post, and it's the one people forget to do.</p>
<h2 id="when-to-stop-and-open-wireshark">When to stop and open Wireshark</h2>
<p><code>tcpdump</code> is for quick answers on the box. For anything involving many connections, throughput analysis, or protocol decode above TCP, capture to a file and open it in Wireshark.</p>
<p>Wireshark's "Expert Information" and its TCP stream graphs will find things you'd miss reading text. <code>tshark</code> gives you the same engine on the command line. And Wireshark's <em>display</em> filters are a different, richer language than the <em>capture</em> filters above — that's a common source of confusion, not a mistake on your part.</p>
<p>Capture on the server, analyse on your laptop. That's the normal workflow, and it's not cheating.</p>
<h2 id="what-did-we-just-build">What did we just build?</h2>
<p>The ability to look at a capture and say what happened, in a sentence, without guessing.</p>
<p>The eight patterns cover the large majority of what you'll meet:</p>
<table>
<thead>
<tr>
<th>What you see</th>
<th>What it is</th>
</tr>
</thead>
<tbody>
<tr>
<td>SYN retransmits, silence</td>
<td>Firewall DROP</td>
</tr>
<tr>
<td>SYN → RST, instant</td>
<td>Nothing listening / REJECT</td>
</tr>
<tr>
<td>Handshake done, SYN-ACK repeats</td>
<td>Accept queue full</td>
</tr>
<tr>
<td>Duplicate ACKs → retransmit</td>
<td>Real path loss</td>
</tr>
<tr>
<td><code>win 0</code></td>
<td>Receiver can't keep up</td>
</tr>
<tr>
<td>RST mid-connection</td>
<td>Crash, or middlebox timeout</td>
</tr>
<tr>
<td>FIN, ACK, FIN, ACK</td>
<td>Healthy close</td>
</tr>
<tr>
<td>Small OK, large stalls</td>
<td>MTU blackhole + blocked ICMP</td>
</tr>
</tbody>
</table>
<p>Part 5: latency. Where it hides, how to attribute it, and a full nginx walkthrough that uses everything in the series at once.</p>
<p>Primary sources: <code>man tcpdump</code>, <code>man pcap-filter</code>, and <a href="https://www.wireshark.org/docs/">wireshark.org</a>.</p>
<hr>
<p>Compiled by AI. Proofread by caffeine. ☕</p>]]></content:encoded>
  </item>
  <item>
    <title>Linux Networking Part 3: Proving Packet Loss</title>
    <link>https://justmeandlinux.com/net-03-proving-packet-loss/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/net-03-proving-packet-loss/</guid>
    <pubDate>Sat, 15 Aug 2026 09:00:00 GMT</pubDate>
    <category>Linux</category>
    <description>&quot;We&apos;re seeing packet loss.&quot; That sentence starts more wasted investigations than any other in infrastructure. It&apos;s not a diagnosis. It&apos;s a feeling. Packets can be discarded at six different places between the wire and your application, and each one has a…</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<p>"We're seeing packet loss."</p>
<p>That sentence starts more wasted investigations than any other in infrastructure. It's not a diagnosis. It's a feeling. Packets can be discarded at six different places between the wire and your application, and each one has a different cause, a different counter, and a different fix.</p>
<p>This post is about replacing the feeling with a number.</p>
<p>Part 1 gave us the ladder. Part 2 covered the socket layer. Now we go <strong>down</strong> — stops 1 through 5, the layers <code>ss</code> can't see, where packets die before any socket ever hears about them.</p>
<h2 id="the-rule">The rule</h2>
<p><strong>A drop counter tells you where. A retransmit counter tells you the path lost something. They are not the same measurement and they are almost never both true at once.</strong></p>
<p>If a router three hops away discards your packet, your NIC's drop counter stays at zero forever. Your NIC never saw the packet. The only evidence you will ever get is that TCP had to send it again.</p>
<p>Conversely, if your NIC's ring buffer overflows, that's not "the network" — that's your machine failing to keep up with traffic that arrived perfectly.</p>
<p>Get this backwards and you'll open a ticket with your network team about a problem that lives entirely inside your own kernel. It happens constantly.</p>
<h2 id="nstat-%E2%80%94-start-here">nstat — start here</h2>
<p><code>nstat</code> reads the kernel's network counters from <code>/proc/net/snmp</code> and <code>/proc/net/netstat</code> and, unlike <code>netstat -s</code>, it's built to show <strong>deltas</strong>.</p>
<p>That matters more than it sounds. A counter reading 4,000,000 retransmits is meaningless — is that from the last minute or from 400 days of uptime? What you need is the rate <em>right now</em>.</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Absolute values, including zeros</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nstat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -az</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Just what changed since the last time you ran it</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nstat</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Sample every second and print what moved</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nstat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -d</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span></span>
<span class="line"></span></code></pre>
<p><code>nstat</code> with no arguments stores a snapshot and prints the difference on the next run. So the workflow during an incident is: run it once to set the baseline, wait, run it again. What prints is what's happening <em>now</em>.</p>
<p>To find a specific counter:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nstat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -az</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> retrans</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nstat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -az</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> listen</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nstat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -az</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> drop</span></span>
<span class="line"></span></code></pre>
<h3 id="the-counters-worth-knowing-by-name">The counters worth knowing by name</h3>
<p><strong>Loss on the path:</strong></p>
<ul>
<li><code>TcpRetransSegs</code> — total segments retransmitted. The headline number for "is the network losing our traffic."</li>
<li><code>TcpExtTCPSynRetrans</code> — retransmitted SYNs specifically. If this is climbing, <em>connection setup</em> is failing, which points at a firewall or an overloaded listener rather than general congestion.</li>
<li><code>TcpExtTCPTimeouts</code> — retransmission timeouts fired. Worse than a fast retransmit: it means TCP waited out the full timer with no acknowledgement at all.</li>
<li><code>TcpExtTCPLostRetransmit</code> — a retransmission was itself lost. Bad path, or serious congestion.</li>
</ul>
<p>Some retransmission is normal on the public internet. What you want is the <strong>ratio</strong>: retransmits against <code>TcpOutSegs</code>. A fraction of a percent is background noise. A few percent is a real problem. Ten percent and users are already complaining.</p>
<p><strong>Listener overload:</strong></p>
<ul>
<li><code>TcpExtListenOverflows</code> — accept queue was full when a handshake completed. Covered in part 2; this is the cumulative proof.</li>
<li><code>TcpExtListenDrops</code> — all drops on listening sockets, overflows included.</li>
</ul>
<p><strong>Receive-side pressure:</strong></p>
<ul>
<li><code>TcpExtTCPBacklogDrop</code> — the socket was locked by its owning process, so incoming packets went to the backlog queue, and that filled. Your application is holding the socket too long.</li>
<li><code>TcpExtPruneCalled</code> / <code>TcpExtRcvPruned</code> — the kernel is under receive-buffer memory pressure and discarding queued data. Rare, and when it shows up it's usually the real answer.</li>
<li><code>TcpExtTCPZeroWindowDrop</code> — data arrived while the receive window was zero. The receiver said "stop" and the sender didn't.</li>
</ul>
<p><strong>Connections dying:</strong></p>
<ul>
<li><code>TcpAttemptFails</code> — outbound connection attempts that failed.</li>
<li><code>TcpEstabResets</code> — established connections terminated by RST.</li>
<li><code>TcpOutRsts</code> — resets <em>we</em> sent. A jump here usually means we're rejecting connections — to a closed port, or from an app slamming connections shut.</li>
</ul>
<p><strong>UDP, which has its own vocabulary:</strong></p>
<ul>
<li><code>UdpNoPorts</code> — datagrams arrived for a port nothing was listening on.</li>
<li><code>UdpInErrors</code> — receive errors.</li>
<li><code>UdpRcvbufErrors</code> — <strong>the important one.</strong> The socket receive buffer was full and datagrams were thrown away. UDP has no retransmission, so this is permanent, silent data loss. If you run DNS, syslog, or statsd, check this counter. People run these services for years without knowing it exists, quietly losing metrics.</li>
<li><code>UdpSndbufErrors</code> — same on the send side.</li>
</ul>
<p>Counter names differ slightly across kernel versions, and new ones appear. <code>nstat -az</code> on the machine in front of you is the authoritative list. Don't trust a name from a blog post — including this one — if grep doesn't find it locally.</p>
<h2 id="interface-counters-%E2%80%94-layers-1-and-2">Interface counters — layers 1 and 2</h2>
<p><code>nstat</code> covers the IP and TCP layers. For the wire itself:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> link</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> show</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth0</span></span>
<span class="line"></span></code></pre>
<p>Two blocks, RX and TX. The exact column headers vary between <code>iproute2</code> versions, so read the header your box prints rather than assuming. The ones you care about:</p>
<ul>
<li><strong>errors</strong> — malformed frames. Checksum failures, framing errors. Real errors here mean physical problems: bad cable, bad SFP, duplex mismatch, dying port. Non-zero and climbing is a hardware conversation.</li>
<li><strong>dropped</strong> — the kernel received the frame and threw it away. Not a hardware error. Usually no buffer space, or no protocol handler.</li>
<li><strong>overrun / missed</strong> — <strong>the ring buffer overflowed.</strong> The NIC had packets and the kernel didn't collect them in time. This is the classic "box can't keep up" signal.</li>
<li><strong>carrier</strong> — link-layer problems. Flapping link.</li>
</ul>
<p>For more detail:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> link</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> show</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth0</span></span>
<span class="line"></span></code></pre>
<p>Same data, broken out further.</p>
<h3 id="ethtool-%E2%80%94-the-drivers-own-numbers">ethtool — the driver's own numbers</h3>
<p><code>ip -s link</code> shows the kernel's generic view. The driver keeps its own, more detailed counters:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ethtool</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -S</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth0</span></span>
<span class="line"></span></code></pre>
<p><strong>These counter names are driver-specific.</strong> An Intel <code>ixgbe</code> card, a Mellanox <code>mlx5</code>, and a virtio NIC in a VM all name things differently. Do not memorise names from a blog; grep what your card actually reports:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ethtool</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -S</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth0</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -E</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'drop|err|miss|discard|fifo'</span></span>
<span class="line"></span></code></pre>
<p>Anything non-zero and climbing there is worth understanding. If a counter name isn't obvious, look it up in that specific driver's documentation — guessing at semantics from the name is how people end up chasing counters that are harmless on their hardware.</p>
<h3 id="ring-buffers">Ring buffers</h3>
<p>If you're seeing overruns, this is the knob:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ethtool</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -g</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth0</span></span>
<span class="line"></span></code></pre>
<p>Prints the hardware maximum and the current setting for RX and TX ring sizes. Vendors frequently ship a default well below the maximum.</p>
<p>Raising the ring size (<code>ethtool -G</code>, syntax in <code>man ethtool</code>) gives the kernel more slack to absorb bursts. It is a genuine fix for burst-driven overruns. It is <strong>not</strong> a fix for sustained overload — a bigger queue that's permanently full just adds latency to the drops. And it's not free: bigger rings mean more memory and can hurt cache behaviour. Change it deliberately, measure before and after.</p>
<h3 id="offloads-%E2%80%94-and-why-your-tcpdump-will-lie">Offloads — and why your tcpdump will lie</h3>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ethtool</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -k</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth0</span></span>
<span class="line"></span></code></pre>
<p>Shows offload features. Two matter for troubleshooting:</p>
<ul>
<li><strong>GRO / LRO</strong> (receive) — the NIC or driver merges multiple incoming segments into one big buffer before the stack sees it.</li>
<li><strong>TSO / GSO</strong> (send) — the stack hands down one huge buffer and the NIC splits it into wire-sized packets.</li>
</ul>
<p>These are good for performance and terrible for your sanity, because <strong><code>tcpdump</code> captures after GRO and before TSO.</strong> You will see 40KB "packets" on a link with a 1500-byte MTU and think you've found something profound. You haven't. You're seeing merged buffers.</p>
<p>Know this now so part 4 doesn't confuse you. If you need to see true wire packets, offloads can be disabled temporarily — check <code>man ethtool</code> for the exact syntax on your version, and remember it will cost throughput while it's off.</p>
<h2 id="the-file-almost-nobody-knows">The file almost nobody knows</h2>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">cat</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /proc/net/softnet_stat</span></span>
<span class="line"></span></code></pre>
<p>One line per CPU, all values in hex. This is stop 2 on the ladder — packet processing in software interrupt context.</p>
<p>The first three columns are the ones to read:</p>
<ol>
<li><strong>processed</strong> — packets this CPU handled.</li>
<li><strong>dropped</strong> — packets dropped because the per-CPU backlog queue was full. Governed by <code>net.core.netdev_max_backlog</code>.</li>
<li><strong>time_squeeze</strong> — the number of times the NAPI poll loop ran out of budget with work still pending.</li>
</ol>
<p>What to look for:</p>
<p><strong>Column 2 non-zero</strong> — you are dropping packets in softirq processing. The interface counters may look fine. Nothing at the socket layer will tell you. This is a real, invisible, easily-missed drop.</p>
<p><strong>Column 3 climbing</strong> — softirq processing is being cut off before it finishes. Under heavy load some of this is expected; growing steadily under normal load is not.</p>
<p><strong>Wildly uneven columns 1 across CPUs</strong> — this is the big one. If CPU 0 processed 400 million packets and CPUs 1-31 processed almost nothing, all your network interrupt handling is landing on a single core. That core saturates, drops packets, and your monitoring shows 3% overall CPU usage because it's averaging across 32 cores.</p>
<p>This is one of the most common serious misconfigurations in Linux networking, and the <em>only</em> place it's visible is this hex file and <code>/proc/interrupts</code>. Multi-queue NICs with proper IRQ affinity spread the load. Check <code>/proc/interrupts</code> for how many queues your card exposes and where they're landing.</p>
<p>Since column 1 is a lifetime total, compare over an interval rather than trusting the absolute numbers:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">watch</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -d</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> cat</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /proc/net/softnet_stat</span></span>
<span class="line"></span></code></pre>
<p><code>-d</code> highlights what changes. Which column is moving tells you the story.</p>
<h2 id="conntrack-%E2%80%94-the-silent-killer">Conntrack — the silent killer</h2>
<p>If your box does NAT, runs containers, or has any stateful firewall rules, the kernel keeps a table of every tracked connection. That table has a hard maximum.</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sysctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> net.netfilter.nf_conntrack_max</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sysctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> net.netfilter.nf_conntrack_count</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">conntrack</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -S</span></span>
<span class="line"></span></code></pre>
<p>The first two are ceiling and current usage. When <code>count</code> approaches <code>max</code>, <strong>new connections get dropped</strong>. Not slowed. Dropped.</p>
<p>The symptom is brutal to diagnose: intermittent connection failures, no application errors, clean interface counters, healthy CPU. <code>conntrack -S</code> shows per-CPU statistics including drops and insert failures. The kernel also logs to dmesg when the table fills:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">dmesg</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> conntrack</span></span>
<span class="line"></span></code></pre>
<p>If you see "table full, dropping packet", you've found it. On container hosts this is a rite of passage. The <code>conntrack-tools</code> package provides the <code>conntrack</code> command; on some systems the sysctl path is <code>net.nf_conntrack_max</code> instead — check what exists on yours.</p>
<h2 id="outbound-drops-%E2%80%94-the-qdisc">Outbound drops — the qdisc</h2>
<p>Everything so far has been receive-side. Transmit has its own queue, and its own drops:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tc</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> qdisc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> show</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dev</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth0</span></span>
<span class="line"></span></code></pre>
<p>Look for <code>dropped</code>, <code>overlimits</code>, and <code>requeues</code> in the statistics. If you have traffic shaping configured, or you're hitting the queue length limit, this is where it shows. On a box with no shaping and drops here, you're sending faster than the link can carry.</p>
<h2 id="sar-%E2%80%94-the-only-tool-that-knows-about-yesterday">sar — the only tool that knows about yesterday</h2>
<p>Every command so far tells you about <em>now</em>. Incidents are about twenty minutes ago, or last Tuesday at 03:00.</p>
<p><code>sar</code> comes from the <code>sysstat</code> package and, when its collector is enabled, records system statistics to disk continuously.</p>
<p>Live:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sar</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> DEV</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        # per-interface throughput and packet rates</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sar</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> EDEV</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">       # per-interface ERRORS and drops</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sar</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> TCP,ETCP</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">   # TCP connection rates, retransmits, resets</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sar</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> SOCK</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">       # socket counts by protocol, including TIME_WAIT</span></span>
<span class="line"></span></code></pre>
<p>Historical — this is the point of the whole tool:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sar</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> DEV</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /var/log/sa/sa13</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">     # data for the 13th of this month</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sar</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> EDEV</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 03:00:00</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -e</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 04:00:00</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /var/log/sa/sa13</span></span>
<span class="line"></span></code></pre>
<p>The log directory path varies by distro (<code>/var/log/sa</code> on RHEL-family, <code>/var/log/sysstat</code> on some Debian-family systems), and on Debian/Ubuntu the collector is often <strong>disabled by default</strong> — installing <code>sysstat</code> isn't enough, you have to enable it. Check <code>/etc/default/sysstat</code>.</p>
<p>Strong opinion, and I'll die on this hill: <strong>enable sysstat on every production box you own, today.</strong> It is a few megabytes a day. The first time an incident resolves itself before you can log in and you can still say exactly what the retransmit rate was at 02:47, it pays for every byte. Every hour spent debugging blind is an hour you chose in advance by not enabling this.</p>
<h2 id="which-counter-answers-which-question">Which counter answers which question</h2>
<p>The table to actually use:</p>
<table>
<thead>
<tr>
<th>Question</th>
<th>Command</th>
<th>What proves it</th>
</tr>
</thead>
<tbody>
<tr>
<td>Is the path losing packets?</td>
<td><code>nstat -az | grep -i retrans</code></td>
<td><code>TcpRetransSegs</code> rising</td>
</tr>
<tr>
<td>Is my NIC overwhelmed?</td>
<td><code>ip -s link show eth0</code></td>
<td>overrun/missed rising</td>
</tr>
<tr>
<td>Is the cable/port bad?</td>
<td><code>ip -s link</code> + <code>ethtool -S</code></td>
<td><code>errors</code>, FCS/CRC counters</td>
</tr>
<tr>
<td>Am I dropping in softirq?</td>
<td><code>cat /proc/net/softnet_stat</code></td>
<td>column 2 non-zero</td>
</tr>
<tr>
<td>Is one CPU doing all the work?</td>
<td><code>/proc/net/softnet_stat</code>, <code>/proc/interrupts</code></td>
<td>column 1 wildly uneven</td>
</tr>
<tr>
<td>Is my app too slow to accept?</td>
<td><code>ss -lnt</code>, <code>nstat | grep -i listen</code></td>
<td><code>TcpExtListenOverflows</code></td>
</tr>
<tr>
<td>Is conntrack full?</td>
<td><code>conntrack -S</code>, <code>dmesg</code></td>
<td>count near max, "table full"</td>
</tr>
<tr>
<td>Am I losing UDP?</td>
<td><code>nstat -az | grep -i Udp</code></td>
<td><code>UdpRcvbufErrors</code></td>
</tr>
<tr>
<td>Am I dropping on send?</td>
<td><code>tc -s qdisc show dev eth0</code></td>
<td><code>dropped</code> rising</td>
</tr>
<tr>
<td>What happened at 3am?</td>
<td><code>sar -n EDEV -f /var/log/sa/saNN</code></td>
<td>historical error rates</td>
</tr>
</tbody>
</table>
<h2 id="which-connection-is-retransmitting">Which connection is retransmitting?</h2>
<p>Counters give you totals. They can't tell you <em>which</em> connection is suffering — and that's usually the question you actually need answered.</p>
<p>Two ways up:</p>
<p><strong>Per-connection, from part 2:</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -tin</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> state</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> established</span></span>
<span class="line"></span></code></pre>
<p>The <code>retrans:</code> field is per-connection. Slower to scan, but it points at a specific peer.</p>
<p><strong>Live tracing.</strong> The BCC tools (package name is usually <code>bcc-tools</code> or <code>bpfcc-tools</code> depending on distro) include <code>tcpretrans</code>, which prints every retransmission as it happens with source, destination, and TCP state. There's also <code>tcpconnect</code>, <code>tcpaccept</code>, and <code>tcplife</code>.</p>
<p>That's the jump from "we have 0.3% retransmits" to "10.0.0.7 is retransmitting to 10.0.0.99 on port 5432." One of those gets fixed today. The tooling here moves fast, so get current syntax from <a href="https://github.com/iovisor/bcc">the BCC repository</a> rather than an old post.</p>
<h2 id="what-did-we-just-build">What did we just build?</h2>
<p>The ability to make a <strong>falsifiable claim</strong>. Not "we're seeing packet loss" but:</p>
<blockquote>
<p>"<code>TcpExtListenOverflows</code> went from 0 to 14,000 in ten minutes while interface errors stayed at zero and retransmits didn't move. The network is fine. Our application stopped accepting connections."</p>
</blockquote>
<p>That's a sentence that ends an argument between two teams. That's the entire job.</p>
<p>The order I'd work in, on a box I've never seen before:</p>
<ol>
<li><code>nstat</code> twice, thirty seconds apart — what's moving?</li>
<li><code>ip -s link</code> — is the wire healthy?</li>
<li><code>ss -lnt</code> — are the listeners keeping up?</li>
<li><code>/proc/net/softnet_stat</code> — is one CPU drowning?</li>
<li><code>conntrack -S</code> if the box does NAT or containers.</li>
</ol>
<p>Five commands, under a minute, and you've eliminated most of the search space.</p>
<p>Part 4: when the counters aren't enough and you have to look at the actual packets. <code>tcpdump</code>, and reading SYN/ACK/RST like a sentence.</p>
<p>Primary sources: <code>man ip</code>, <code>man ethtool</code>, <code>man nstat</code>, <code>man sar</code>, and <code>Documentation/networking/</code> in the kernel tree.</p>
<hr>
<p>Compiled by AI. Proofread by caffeine. ☕</p>]]></content:encoded>
  </item>
  <item>
    <title>Linux Networking Part 2: Reading Sockets with ss</title>
    <link>https://justmeandlinux.com/net-02-reading-sockets-with-ss/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/net-02-reading-sockets-with-ss/</guid>
    <pubDate>Sat, 15 Aug 2026 08:00:00 GMT</pubDate>
    <category>Linux</category>
    <description>You have nginx running. Someone says it&apos;s broken. You need to know, in the next thirty seconds: who is connected, from where, to which port, and whether anything is stuck. One command answers all of that. Most people run it wrong. This is part 2 of the…</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<p>You have nginx running. Someone says it's broken. You need to know, in the next thirty seconds: who is connected, from where, to which port, and whether anything is stuck.</p>
<p>One command answers all of that. Most people run it wrong.</p>
<p>This is part 2 of the series. Part 1 built the map — the seven-stop path from wire to application. <code>ss</code> reads stops <strong>6 and 7</strong>: the socket layer and how well your application is keeping up with it.</p>
<h2 id="why-ss-and-not-netstat">Why ss and not netstat</h2>
<p><code>ss</code> ships with <code>iproute2</code> — the same package as <code>ip</code>. It reads socket state from the kernel over a netlink socket.</p>
<p><code>netstat</code> comes from <code>net-tools</code>, which has been effectively unmaintained for years and isn't installed by default on most modern distros. It works by parsing <code>/proc/net/tcp</code> as text.</p>
<p>On an idle box the difference is invisible. On a box with 200,000 sockets, <code>netstat -an</code> takes ten seconds and burns CPU you can't spare during an incident. <code>ss</code> returns immediately. When you're the one holding the pager, that gap is the whole argument.</p>
<p>Use <code>ss</code>. Recognise <code>netstat</code> in old docs. Don't write new runbooks with it.</p>
<h2 id="the-flags-that-matter">The flags that matter</h2>
<p>Six letters cover 90% of real use:</p>
<pre data-lang-none><code>-t   TCP sockets
-u   UDP sockets
-a   all sockets (listening AND established)
-l   listening sockets only
-n   numeric — do not resolve ports or hostnames
-p   show the process holding the socket (needs root for other users' sockets)
</code></pre>
<p><strong>Always pass <code>-n</code>.</strong> Without it, <code>ss</code> does a DNS lookup for every peer address. On a busy host that turns an instant command into a thirty-second hang — and if DNS is the thing that's broken, your diagnostic tool hangs on the outage you're diagnosing. Skipping <code>-n</code> is the single most common way people make a bad day worse.</p>
<p>The two commands you'll type most:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># What is this box listening on?</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -lntp</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Who is connected right now?</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -antp</span></span>
<span class="line"></span></code></pre>
<h2 id="reading-the-output">Reading the output</h2>
<pre data-lang-none><code>State   Recv-Q  Send-Q   Local Address:Port    Peer Address:Port   Process
LISTEN  0       511      0.0.0.0:80            0.0.0.0:*           users:(("nginx",pid=1234,fd=6))
ESTAB   0       0        10.0.0.5:80           203.0.113.7:51234   users:(("nginx",pid=1235,fd=12))
ESTAB   4096    0        10.0.0.5:80           203.0.113.9:51402   users:(("nginx",pid=1235,fd=13))
</code></pre>
<p>Six columns. Each one earns its place.</p>
<p><strong>State</strong> — where this socket sits in the TCP state machine. Full list below.</p>
<p><strong>Local Address:Port</strong> — your side. <code>0.0.0.0:80</code> means "port 80 on every IPv4 address on this machine." <code>127.0.0.1:80</code> means loopback only — nothing outside the box can reach it, ever. That one line has explained more "but the service is running!" tickets than anything else in this post. <code>[::]:80</code> is the IPv6 equivalent of <code>0.0.0.0</code>.</p>
<p><strong>Peer Address:Port</strong> — the other side's IP and port. <strong>This is your answer to "who is connecting to me and on which port."</strong> No extra tooling needed.</p>
<p><strong>Recv-Q and Send-Q</strong> — these mean two different things depending on the state, and this is the part everyone gets wrong.</p>
<h3 id="recv-q-send-q-on-an-established-socket">Recv-Q / Send-Q on an ESTABLISHED socket</h3>
<ul>
<li><strong>Recv-Q</strong> — bytes sitting in the kernel receive buffer that your application has not <code>read()</code> yet.</li>
<li><strong>Send-Q</strong> — bytes your application has written that the kernel has not yet got acknowledged by the peer.</li>
</ul>
<p>So in that third line above, <code>Recv-Q 4096</code> says: 4KB arrived from <code>203.0.113.9</code>, and nginx hasn't picked it up.</p>
<p>The diagnostic split is clean:</p>
<table>
<thead>
<tr>
<th>Symptom</th>
<th>Meaning</th>
<th>Where to look</th>
</tr>
</thead>
<tbody>
<tr>
<td>Recv-Q climbing on many sockets</td>
<td>Your app is not reading fast enough</td>
<td>The application. Threads, event loop, blocked on I/O or a downstream call.</td>
</tr>
<tr>
<td>Send-Q climbing on many sockets</td>
<td>Kernel can't get data out</td>
<td>Network path, receiver's window, congestion, a slow client.</td>
</tr>
<tr>
<td>Send-Q climbing on <strong>one</strong> socket</td>
<td>That one peer is slow or gone</td>
<td>That client. Bad WiFi, dead host, tiny receive window.</td>
</tr>
</tbody>
</table>
<p>A brief spike in either is normal — that's what buffers are for. <strong>Sustained and growing</strong> is the signal. Watch it over time rather than judging a single snapshot:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">watch</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n1</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'ss -ant state established | head -30'</span></span>
<span class="line"></span></code></pre>
<h3 id="recv-q-send-q-on-a-listening-socket">Recv-Q / Send-Q on a LISTENING socket</h3>
<p>Completely different meaning. Here's where the real gold is.</p>
<ul>
<li><strong>Recv-Q</strong> — the number of established connections currently sitting in the accept queue, waiting for your application to call <code>accept()</code>.</li>
<li><strong>Send-Q</strong> — the maximum size of that accept queue.</li>
</ul>
<p>So:</p>
<pre data-lang-none><code>LISTEN  0    511    0.0.0.0:80
</code></pre>
<p>means: queue capacity 511, currently zero waiting. Healthy.</p>
<pre data-lang-none><code>LISTEN  511  511    0.0.0.0:80
</code></pre>
<p>means: <strong>the accept queue is completely full.</strong> New connections are being dropped right now. Your NIC counters are clean, the network is fine, and clients are hanging or getting resets.</p>
<p>That one line is worth the whole post. Check it before you touch <code>tcpdump</code>.</p>
<h2 id="the-two-queues-nobody-separates">The two queues nobody separates</h2>
<p>When a client connects to your server, the connection passes through <em>two</em> kernel queues. Confusing them makes overload impossible to diagnose.</p>
<pre data-lang-none><code>client SYN
    |
    v
[ SYN queue ]  ---- half-open. Handshake in progress. State: SYN-RECV.
    |             sized via net.ipv4.tcp_max_syn_backlog
    |  (handshake completes)
    v
[ accept queue ] -- fully established, waiting for the app.
    |               sized via min(listen() backlog, net.core.somaxconn)
    |  (app calls accept())
    v
your application
</code></pre>
<p><strong>SYN queue full</strong> — usually a SYN flood, or a genuine connection storm. If <code>net.ipv4.tcp_syncookies</code> is enabled (it is by default on most distros), the kernel falls back to SYN cookies instead of dropping, which is exactly what you want.</p>
<p><strong>Accept queue full</strong> — your application is too slow to <code>accept()</code>. This is far more common and far more misdiagnosed. The kernel completed the handshake, the connection is <em>established and ready</em>, and your app never picked it up.</p>
<p>What happens when the accept queue overflows depends on a sysctl:</p>
<ul>
<li><code>net.ipv4.tcp_abort_on_overflow = 0</code> (the default) — the kernel silently ignores the client's final ACK. The client thinks it's connected, waits, retransmits. The user experiences a hang. <strong>Nothing in your logs mentions it.</strong></li>
<li><code>net.ipv4.tcp_abort_on_overflow = 1</code> — the kernel sends a RST. The client fails fast with "connection reset by peer."</li>
</ul>
<p>Default 0 is why this is so hard to spot. The failure mode is designed to be invisible and hope the app catches up.</p>
<p>Two places to see it. First, live:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -lnt</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">          # Recv-Q at or near Send-Q on a listener</span></span>
<span class="line"></span></code></pre>
<p>Second, cumulative:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nstat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -az</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> listen</span></span>
<span class="line"></span></code></pre>
<p><code>TcpExtListenOverflows</code> counts accept-queue overflows. <code>TcpExtListenDrops</code> counts all drops on listening sockets. <strong>If <code>ListenOverflows</code> is climbing, stop looking at the network.</strong> Full detail on <code>nstat</code> in part 3.</p>
<p>For nginx specifically the queue size comes from the <code>backlog</code> parameter on the <code>listen</code> directive, capped by <code>net.core.somaxconn</code>. Raising one without the other does nothing — the kernel takes the minimum. Check the current values on your box with <code>sysctl net.core.somaxconn</code> and confirm the nginx side against <a href="https://nginx.org/en/docs/http/ngx_http_core_module.html#listen">the nginx listen documentation</a> rather than trusting any blog's numbers, including this one.</p>
<p>And a caveat worth saying plainly: a full accept queue is a <em>symptom</em>. Raising the backlog buys you a deeper queue to fall behind in. Find out why the app stopped accepting.</p>
<h2 id="the-tcp-states-and-what-each-one-means-when-it-piles-up">The TCP states, and what each one means when it piles up</h2>
<p>You'll see these in the State column. Individually they're routine. In bulk, each one is a specific diagnosis.</p>
<table>
<thead>
<tr>
<th>State</th>
<th>Meaning</th>
<th>What a pile-up tells you</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>LISTEN</code></td>
<td>Waiting for connections</td>
<td>Normal.</td>
</tr>
<tr>
<td><code>SYN-SENT</code></td>
<td>We sent SYN, no reply yet</td>
<td>Many = you can't reach the destination. Firewall, dead host, wrong port.</td>
</tr>
<tr>
<td><code>SYN-RECV</code></td>
<td>We got SYN, handshake unfinished</td>
<td>Many = SYN flood, or clients disappearing mid-handshake.</td>
</tr>
<tr>
<td><code>ESTAB</code></td>
<td>Connected</td>
<td>Normal — but count them.</td>
</tr>
<tr>
<td><code>FIN-WAIT-1</code></td>
<td>We sent FIN, not yet acked</td>
<td>Many = peer isn't responding to teardown.</td>
</tr>
<tr>
<td><code>FIN-WAIT-2</code></td>
<td>Our FIN acked, waiting for theirs</td>
<td>Many = peer isn't closing its end.</td>
</tr>
<tr>
<td><code>CLOSE-WAIT</code></td>
<td><strong>Peer closed. We haven't.</strong></td>
<td>Many = <strong>an application bug.</strong> See below.</td>
</tr>
<tr>
<td><code>LAST-ACK</code></td>
<td>We sent our FIN after theirs</td>
<td>Transient.</td>
</tr>
<tr>
<td><code>TIME-WAIT</code></td>
<td>We closed first, waiting out stragglers</td>
<td>Often normal. See below.</td>
</tr>
<tr>
<td><code>CLOSING</code></td>
<td>Simultaneous close</td>
<td>Rare.</td>
</tr>
</tbody>
</table>
<p>Two of these deserve their own section.</p>
<h3 id="closewait-is-always-your-fault">CLOSE_WAIT is always your fault</h3>
<p><code>CLOSE_WAIT</code> means the remote end sent a FIN and the kernel acknowledged it — and your application has not called <code>close()</code> on that file descriptor.</p>
<p>The kernel cannot fix this. It has done its job and is waiting on you. <code>CLOSE_WAIT</code> sockets do not time out. They sit there until the process closes the descriptor or dies.</p>
<p>Thousands of <code>CLOSE_WAIT</code> sockets on one process means that process is leaking file descriptors, and it will eventually hit its <code>ulimit -n</code> and start failing to accept anything at all — which looks, from outside, exactly like a network outage.</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -antp</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> state</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> close-wait</span></span>
<span class="line"></span></code></pre>
<p>If that returns a large number pointing at one PID, you have found a bug in that application. Not in the network. Restarting clears it; only a code fix stops it coming back.</p>
<h3 id="timewait-is-usually-not-your-fault">TIME_WAIT is usually not your fault</h3>
<p><code>TIME_WAIT</code> appears on whichever side closed the connection <em>first</em>. The socket waits out a fixed interval — 60 seconds on Linux, compiled in, not tunable by sysctl — so that delayed packets from the old connection can't be misdelivered into a new one reusing the same 4-tuple.</p>
<p>Servers usually let clients close, so servers usually don't accumulate them. A load balancer or proxy that closes its own upstream connections does, and 20,000 <code>TIME_WAIT</code> entries there can be completely healthy.</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -tan</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> state</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> time-wait</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> wc</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -l</span></span>
<span class="line"></span></code></pre>
<p>It only becomes a real problem when you're making a very high rate of <em>outbound</em> connections to the same destination and exhausting source ports. That's part 5.</p>
<p>One hard rule: <strong>do not enable <code>net.ipv4.tcp_tw_recycle</code>.</strong> It broke connections from clients behind NAT, and it was removed from the kernel in 4.12 — so on any modern system the sysctl doesn't even exist. Any tuning guide that recommends it was written before 2017 and should be closed. <code>net.ipv4.tcp_tw_reuse</code> is a different and much safer setting; read <code>tcp(7)</code> before touching it.</p>
<h2 id="filtering-%E2%80%94-get-to-the-answer-directly">Filtering — get to the answer directly</h2>
<p>Piping <code>ss</code> into <code>grep</code> works, and it's how most people do it. <code>ss</code> has a real filter language that's faster and doesn't produce false matches on a port number that happens to appear in an IP address.</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Everything talking to one host</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -antp</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dst</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 203.0.113.7</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Everyone connected to my port 443</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -antp</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '( dport = :443 or sport = :443 )'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Only established connections</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -antp</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> state</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> established</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Established, to one specific client</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -antp</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> state</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> established</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dst</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 203.0.113.7</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># How many connections per client IP — the "who is hammering me" one-liner</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -ant</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> state</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> established</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> awk</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '{print $5}'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> cut</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -d:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f1</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> sort</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> uniq</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -c</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> sort</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -rn</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> head</span></span>
<span class="line"></span></code></pre>
<p>That last one is the command to run when traffic spikes and you want to know whether it's one bad actor or genuine load. It answers the question in a second.</p>
<p>And for a fast overview:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span></span>
<span class="line"></span></code></pre>
<p>Totals by protocol and state. Good for "is this box holding 400 connections or 400,000?" before you decide where to dig.</p>
<h2 id="looking-inside-a-connection">Looking inside a connection</h2>
<p>This is where <code>ss</code> stops being a listing tool and becomes a diagnostic one:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -tin</span></span>
<span class="line"></span></code></pre>
<p><code>-i</code> dumps the kernel's internal TCP state per connection. The output is dense. The fields worth knowing:</p>
<ul>
<li><strong><code>rtt:12.5/6.25</code></strong> — smoothed round-trip time and its variance, in milliseconds. <strong>This is measured RTT on the real connection</strong>, not a synthetic <code>ping</code>. It reflects the actual path this traffic takes, including any middleboxes.</li>
<li><strong><code>minrtt</code></strong> — the lowest RTT ever seen on this connection. Compare with <code>rtt</code>: if current RTT is far above minrtt, you're queueing somewhere.</li>
<li><strong><code>retrans:0/12</code></strong> — retransmits currently outstanding / total on this connection. <strong>A non-zero total is direct evidence of loss on the path.</strong></li>
<li><strong><code>cwnd</code></strong> — congestion window, in segments. How much the kernel is willing to have in flight. A small cwnd on a fast link means the stack has recently detected loss and backed off.</li>
<li><strong><code>bytes_sent</code> / <code>bytes_acked</code> / <code>bytes_received</code></strong> — lifetime totals for this connection.</li>
<li><strong><code>send 1.2Mbps</code></strong> / <strong><code>delivery_rate</code></strong> — the stack's own estimate of throughput.</li>
<li><strong><code>lastsnd</code> / <code>lastrcv</code> / <code>lastack</code></strong> — milliseconds since the last send, receive, and ack. Large values on a supposedly active connection mean it's idle or stuck.</li>
<li><strong><code>mss</code> / <code>pmtu</code></strong> — segment size and path MTU. Relevant when you suspect MTU problems, which get their own treatment in part 4.</li>
</ul>
<p>Field names vary a little between kernel and iproute2 versions, and new ones get added. <code>man ss</code> on the box you're standing on is the authority.</p>
<p>The practical use: pick a slow connection and inspect it.</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -tin</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> state</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> established</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dst</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 203.0.113.7</span></span>
<span class="line"></span></code></pre>
<p>If <code>rtt</code> is 200ms and <code>retrans</code> is climbing, the path is bad and you can say so with evidence. If <code>rtt</code> is 0.4ms and <code>retrans</code> is 0 and the user still says it's slow, the network is exonerated — go look at the application. Being able to make that call confidently, in one command, is most of what this series is for.</p>
<h2 id="what-did-we-just-build">What did we just build?</h2>
<p>A complete picture of stops 6 and 7 on the map:</p>
<ul>
<li><strong>Who's connected</strong> — the Peer Address column.</li>
<li><strong>Is my app keeping up</strong> — Recv-Q on established sockets, Recv-Q vs Send-Q on listeners.</li>
<li><strong>Is my app leaking</strong> — <code>CLOSE_WAIT</code> counts.</li>
<li><strong>Is the path lossy</strong> — <code>retrans</code> and <code>rtt</code> from <code>ss -ti</code>.</li>
</ul>
<p>Notice what's missing. <code>ss</code> tells you nothing about packets dropped at the NIC, in softirq processing, or by conntrack — all of which happen <em>below</em> the socket layer and never reach a socket to be counted. If <code>ss</code> looks clean and things are still broken, that's your signal to go down the ladder.</p>
<p>Which is part 3: proving packet loss with counters.</p>
<p>Primary sources: <code>man ss</code>, <code>man 7 tcp</code>.</p>
<hr>
<p>Compiled by AI. Proofread by caffeine. ☕</p>]]></content:encoded>
  </item>
  <item>
    <title>Linux Networking Part 1: The Mental Model</title>
    <link>https://justmeandlinux.com/net-01-mental-model/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/net-01-mental-model/</guid>
    <pubDate>Sat, 15 Aug 2026 07:00:00 GMT</pubDate>
    <category>Linux</category>
    <description>Most engineers learn Linux networking backwards. They memorise commands first, then spend years never quite knowing which one to reach for. So they run netstat -an , stare at 4000 lines, and guess. The fix is not more commands. The fix is a map. Once you know…</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<p>Most engineers learn Linux networking backwards. They memorise commands first, then spend years never quite knowing which one to reach for. So they run <code>netstat -an</code>, stare at 4000 lines, and guess.</p>
<p>The fix is not more commands. The fix is a map. Once you know the path a packet takes from the wire to your application, every tool slots into a specific point on that path, and "which command do I use?" answers itself.</p>
<p>This is part one of a series on troubleshooting connectivity, packet loss, and latency on Linux. This post is all model, almost no commands. That is deliberate. The commands in parts 2 through 5 will be useless to you without it.</p>
<h2 id="what-a-socket-actually-is">What a socket actually is</h2>
<p>Start here, because almost everything else is downstream of it.</p>
<p>A socket is a kernel data structure. That's it. It is not a connection, it is not a port, and it is not a file — though the kernel deliberately makes it look like a file so you can <code>read()</code> and <code>write()</code> it.</p>
<p>When your process calls <code>socket()</code>, the kernel allocates that structure and hands you back a file descriptor — an integer that indexes into your process's file descriptor table. Everything you do afterward, you do through that integer.</p>
<p>The important part: <strong>the socket owns two buffers.</strong></p>
<ul>
<li>A <strong>receive buffer</strong>. The kernel puts incoming data here. Your app drains it by calling <code>read()</code>/<code>recv()</code>.</li>
<li>A <strong>send buffer</strong>. Your app fills it by calling <code>write()</code>/<code>send()</code>. The kernel drains it onto the network.</li>
</ul>
<p>Hold onto that, because it explains the single most useful pair of numbers in Linux networking:</p>
<ul>
<li><strong>Recv-Q filling up</strong> means data arrived and your application is not reading it fast enough. That is an <em>application</em> problem.</li>
<li><strong>Send-Q filling up</strong> means your application wrote data and the kernel cannot get it onto the network fast enough. That is a <em>network</em> problem — or a receiver problem.</li>
</ul>
<p>Two columns. Two completely different on-call pages. We'll read them for real in part 2.</p>
<h3 id="stream-datagram-raw">Stream, datagram, raw</h3>
<p>When you create a socket you pick a type. The three that matter:</p>
<p><strong><code>SOCK_STREAM</code></strong> — TCP. A reliable, ordered, bidirectional byte stream. Emphasis on <em>byte stream</em>: TCP has no concept of messages. If the sender writes 100 bytes then 100 bytes, the receiver might read 200 bytes at once, or 37 then 163. Every framing bug in every network protocol ever written comes from someone forgetting this. TCP guarantees the bytes arrive, in order, exactly once. It guarantees nothing about how they're grouped.</p>
<p><strong><code>SOCK_DGRAM</code></strong> — UDP. Messages, preserved as units. Send a 100-byte datagram, and the receiver reads exactly 100 bytes or nothing. No ordering, no delivery guarantee, no retransmission. Which is a feature: DNS, QUIC, and most video use it precisely because they'd rather handle loss themselves than wait for TCP's retransmit.</p>
<p><strong><code>SOCK_RAW</code></strong> — you build the packet headers yourself. This is what <code>ping</code> and <code>tcpdump</code>-adjacent tooling use. Needs privilege. You will almost never write one, but knowing it exists explains why some tools need root.</p>
<p>There's also <code>SOCK_SEQPACKET</code> — reliable and ordered like TCP, but message-preserving like UDP. Real, used by SCTP and Unix domain sockets, rare in practice. Mentioned so the name isn't a mystery when you meet it.</p>
<h3 id="the-4-tuple-%E2%80%94-the-one-thing-to-memorise">The 4-tuple — the one thing to memorise</h3>
<p>A TCP connection is uniquely identified by four values:</p>
<pre data-lang-none><code>source IP : source port  -&gt;  destination IP : destination port
</code></pre>
<p>That's the 4-tuple. The kernel uses it as a lookup key. Every packet that arrives gets matched against this key to decide which socket it belongs to.</p>
<p>Three consequences fall straight out of this, and all three show up in production:</p>
<p><strong>One.</strong> Your web server can hold 50,000 connections on port 443. Port 443 isn't "used up" by the first one. Each connection has a different client IP or client port, so each is a distinct tuple. People find this confusing for years. It's just a composite key.</p>
<p><strong>Two.</strong> A client machine making outbound connections to a <em>single</em> destination IP and port is limited by its own source ports — roughly 28,000 by default (<code>net.ipv4.ip_local_port_range</code>). Hit that ceiling and new connections fail while the box looks completely idle. This is ephemeral port exhaustion, and it's one of the great "the server is fine, why can't we connect" mysteries. Part 5.</p>
<p><strong>Three.</strong> <code>TIME_WAIT</code> sockets hold a tuple hostage for 60 seconds after close. Thousands of them are usually normal. Sometimes they're not. Part 2.</p>
<h2 id="the-path-a-packet-takes">The path a packet takes</h2>
<p>Here is the whole journey, inbound. Every troubleshooting tool you'll ever use plugs in at one of these stops.</p>
<pre data-lang-none><code>   the wire
      |
 [1]  NIC hardware  ........ ring buffer, offloads, link errors
      |
 [2]  driver / softirq ..... NAPI polling, per-CPU processing
      |
 [3]  netfilter / conntrack  iptables, nftables, connection tracking
      |
 [4]  routing ............. which interface, which next hop
      |
 [5]  TCP/IP stack ........ checksums, reassembly, TCP state machine
      |
 [6]  socket receive buffer  the kernel's holding pen for your app
      |
 [7]  your application ..... read() / recv() / accept()
</code></pre>
<p>Read it once more. That ladder is the whole series.</p>
<p><strong>[1] The NIC.</strong> Packets land in a ring buffer — a fixed-size queue in memory the card writes into. If the kernel doesn't drain it fast enough, the card overwrites or discards. That's a drop, and crucially it happens <em>before</em> anything else can see it. Nothing above this layer will tell you it happened. You need <code>ethtool</code> and <code>ip -s link</code> counters, which is part 3.</p>
<p><strong>[2] Driver and softirq.</strong> The kernel processes received packets in software interrupt context, on a specific CPU. If that CPU is saturated — and it can be saturated while 31 other cores idle — you drop packets. <code>/proc/net/softnet_stat</code> is the file that tells you. Almost nobody knows it exists.</p>
<p><strong>[3] netfilter and conntrack.</strong> Firewall rules run here. Connection tracking, which is what makes NAT and stateful rules possible, keeps a table with a <em>maximum size</em>. When that table fills, new connections get dropped — silently, as far as the application is concerned. If you run containers or NAT, this bites eventually.</p>
<p><strong>[4] Routing.</strong> Does the kernel even know how to reach that destination? Which interface goes out? Asymmetric routing and policy routing live here.</p>
<p><strong>[5] The TCP stack.</strong> The state machine, retransmissions, window management, congestion control. Where "packet loss" in the classic sense becomes visible as retransmits.</p>
<p><strong>[6] The socket buffer.</strong> Data has arrived and is correct. It's sitting in the kernel, waiting. If it sits too long, it's because of [7].</p>
<p><strong>[7] Your application.</strong> It has to actually call <code>read()</code>. Or, for a listening socket, <code>accept()</code>. If it doesn't — because it's blocked on a database query, or the thread pool is exhausted, or it's stuck in GC — the queues back up beneath it and the symptom looks exactly like a network problem. It isn't.</p>
<p>Outbound is the same ladder in reverse, with a different key failure: the qdisc (queueing discipline) between the stack and the driver, where traffic shaping and its own drops live.</p>
<h2 id="the-three-questions">The three questions</h2>
<p>Every connectivity investigation is one of three questions. Knowing which one you're asking saves an hour.</p>
<p><strong>"Can they reach me at all?"</strong> — connectivity. Is the port listening, is the packet arriving, is a firewall eating it, is routing sane. Tools: <code>ss -lnt</code>, <code>tcpdump</code>, <code>ip route get</code>, firewall rules.</p>
<p><strong>"Are we losing traffic?"</strong> — loss. Not "does it feel slow" but <em>counters</em>, at a specific layer. Tools: <code>nstat</code>, <code>ip -s link</code>, <code>ethtool -S</code>, <code>/proc/net/softnet_stat</code>.</p>
<p><strong>"Why is it slow?"</strong> — latency. Which almost always means: something is queueing. Find the queue. Tools: <code>ss -ti</code>, <code>ping</code>/<code>mtr</code>, <code>sar</code>, and reading Recv-Q/Send-Q.</p>
<p>Mixing these up is the classic failure. Someone reports slowness, and an engineer spends two hours in <code>tcpdump</code> proving packets arrive — which was never in doubt. The question was never connectivity.</p>
<h2 id="why-packet-loss-is-usually-the-wrong-phrase">Why "packet loss" is usually the wrong phrase</h2>
<p>When someone says "we have packet loss," ask where.</p>
<ul>
<li>Dropped at the NIC because the ring buffer overflowed? That's <em>your box being overwhelmed</em>. Fix locally.</li>
<li>Dropped in the network between two hosts? That shows up as <strong>TCP retransmissions</strong>, not as a drop counter on your interface. Your interface never saw the packet.</li>
<li>Dropped because the accept queue was full? The client sees a hang or a reset. Your NIC counters are pristine. Nothing is wrong with the network at all.</li>
<li>Dropped by a firewall rule? Nothing anywhere reports "loss." The packet was deliberately discarded and your counters are clean.</li>
</ul>
<p>Four completely different problems, one phrase. This is why the ladder matters more than the tools: it turns "packet loss" into a location.</p>
<p><strong>What did we just build?</strong> A map with seven stops and a rule: before you run anything, decide which stop you're investigating. From here on, every tool in this series is introduced as <em>"this reads layer N"</em>.</p>
<h2 id="which-tools-are-worth-your-time">Which tools are worth your time</h2>
<p>Blunt opinion, since you'll meet all of these:</p>
<p><strong>Learn these.</strong> <code>ss</code> (sockets and TCP state — replaces <code>netstat</code> entirely and is dramatically faster on busy hosts), <code>ip</code> (addresses, routes, link counters), <code>nstat</code> (kernel network counters, with deltas), <code>tcpdump</code> (what's actually on the wire), <code>ethtool</code> (NIC-level truth), <code>sar</code> (historical data — the only one of these that answers "what happened at 3am").</p>
<p><strong>Know they exist.</strong> <code>netstat</code>, <code>ifconfig</code>, <code>route</code> — deprecated, from the <code>net-tools</code> package, often not even installed on modern distros. Read them in old runbooks; don't write new ones with them. <code>netstat -s</code> is the one genuinely worth keeping around, and <code>nstat</code> does the same job better.</p>
<p><strong>The upgrade path.</strong> <code>bpftrace</code> and the BCC tools (<code>tcpretrans</code>, <code>tcpconnect</code>, <code>tcplife</code>). These let you ask questions the counters can't answer — <em>which</em> connection retransmitted, not just how many. This is the line between senior and everyone else. Later in the series.</p>
<p>One opinion I'll defend: <strong><code>sar</code> is the most underrated tool on this list.</strong> Every other tool tells you about now. Incidents are about 20 minutes ago. If <code>sysstat</code> isn't collecting on your production boxes, you are choosing to investigate every future incident with one hand tied.</p>
<h2 id="next">Next</h2>
<p>Part 2: <code>ss</code> properly. Who is connected to you right now, from which IP, on which port, in which TCP state — and what those two queue columns are trying to tell you.</p>
<p>Reference for anything here: the <code>socket(2)</code>, <code>tcp(7)</code>, and <code>ip(7)</code> man pages are the primary sources and are better written than most blog posts, this one included.</p>
<hr>
<p>Compiled by AI. Proofread by caffeine. ☕</p>]]></content:encoded>
  </item>
  <item>
    <title>Linux Networking Part 0: The Fundamentals</title>
    <link>https://justmeandlinux.com/net-00-fundamentals/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/net-00-fundamentals/</guid>
    <pubDate>Sat, 15 Aug 2026 06:00:00 GMT</pubDate>
    <category>Linux</category>
    <description>Somebody says &quot;the app can&apos;t reach the database.&quot; You have a terminal and no idea where to start. This post is the on-ramp. Vocabulary, then the seven commands that answer &quot;is it reachable, is the port open, is DNS working, is TLS working, and who owns this…</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<p>Somebody says "the app can't reach the database." You have a terminal and no idea where to start.</p>
<p>This post is the on-ramp. Vocabulary, then the seven commands that answer "is it reachable, is the port open, is DNS working, is TLS working, and who owns this socket." No kernel counters, no packet analysis — those come later in the series. Just the ground floor, done properly.</p>
<p>If you're already comfortable with <code>dig</code> and <code>nc</code>, skim the glossary and skip to part 1. If you're not, this is the post that makes the rest of the series readable.</p>
<h2 id="glossary">Glossary</h2>
<p>Get these straight once and a lot of confusion evaporates.</p>
<p><strong>Interface</strong> — a network device as the kernel sees it. <code>eth0</code>, <code>ens3</code>, <code>lo</code>, <code>docker0</code>. Might be physical hardware, might be entirely virtual.</p>
<p><strong>Loopback (<code>lo</code>, <code>127.0.0.1</code>)</strong> — the interface that talks to the machine itself. Traffic on it never reaches a wire. A service bound to <code>127.0.0.1</code> is unreachable from anywhere else, permanently, no matter what your firewall says.</p>
<p><strong>IP address</strong> — identifies a host. <code>192.0.2.10</code>.</p>
<p><strong>Subnet / CIDR</strong> — <code>192.0.2.0/24</code> means the first 24 bits are the network part, leaving 256 addresses. Hosts in the same subnet reach each other directly over layer 2; anything else goes via a gateway.</p>
<p><strong>Gateway / default route</strong> — where packets go when the destination isn't in a directly-connected subnet. "The way out."</p>
<p><strong>Port</strong> — a 16-bit number (1–65535) identifying which service on a host. Not a physical thing.</p>
<p><strong>Well-known vs ephemeral ports</strong> — below 1024 are the reserved ones (22 SSH, 53 DNS, 80 HTTP, 443 HTTPS) and binding them traditionally requires privilege. The high range is the <strong>ephemeral</strong> pool the kernel picks from for outbound connections.</p>
<p><strong>Socket</strong> — the kernel structure representing one endpoint of a communication. Part 1 goes deep.</p>
<p><strong>The 4-tuple</strong> — <code>source IP : source port → destination IP : destination port</code>. The unique identity of a TCP connection. The single most useful concept in this entire series.</p>
<p><strong>Listening vs established</strong> — a <em>listening</em> socket waits for connections. An <em>established</em> socket is one live connection. One listener on port 443 can have 50,000 established connections beneath it.</p>
<p><strong>TCP</strong> — reliable, ordered, connection-oriented. Handshake first, retransmits what's lost. A byte stream, not messages.</p>
<p><strong>UDP</strong> — fire and forget. No handshake, no retransmission, no ordering. Messages preserved as units. Fast, and lossy by design.</p>
<p><strong>ICMP</strong> — the control protocol. Error and diagnostic messages: <code>ping</code>, "destination unreachable", "fragmentation needed". <strong>Not TCP or UDP, and it has no ports.</strong> Blocking it wholesale breaks things people don't expect.</p>
<p><strong>Frame / packet / segment / datagram</strong> — the same data at different layers. <em>Frame</em> on the wire (layer 2, has MAC addresses). <em>Packet</em> at the IP layer (layer 3). <em>Segment</em> is TCP's unit, <em>datagram</em> is UDP's (layer 4). People use them interchangeably; being precise helps when reading man pages.</p>
<p><strong>MAC address</strong> — layer 2 hardware address, <code>02:fc:00:00:00:05</code>. Only meaningful within a single network segment.</p>
<p><strong>ARP</strong> — how a host discovers the MAC address for an IP on its local segment. Part 6.</p>
<p><strong>MTU</strong> — the largest packet an interface will send, typically 1500 bytes on Ethernet. Tunnels and VPNs reduce it, and that causes some of the nastiest bugs in networking.</p>
<p><strong>MSS</strong> — the largest TCP payload per segment. Roughly MTU minus IP and TCP headers.</p>
<p><strong>RTT</strong> — round-trip time. How long a packet takes to get there and back.</p>
<p><strong>TTL</strong> — a hop counter in the IP header. Each router decrements it; at zero the packet is discarded and an ICMP error is returned. This is the mechanism <code>traceroute</code> exploits.</p>
<p><strong>NAT</strong> — rewriting addresses in flight, so many private hosts share one public IP. Your home router does it. So does Docker.</p>
<p><strong>DNS</strong> — names to addresses.</p>
<p><strong>TLS</strong> — the encryption layer under HTTPS. A separate handshake <em>after</em> the TCP handshake, which is why it gets its own timing measurement.</p>
<h2 id="the-order-to-check-things">The order to check things</h2>
<p>Work bottom-up. Each step assumes the one before it passed.</p>
<pre data-lang-none><code>1. Is the interface up and does it have an address?   ip -br addr
2. Do I have a route to the destination?              ip route get &lt;ip&gt;
3. Does the host respond at all?                      ping
4. Does the name resolve?                             dig / getent
5. Is the port open?                                  nc -zv
6. Does TLS work?                                     openssl s_client
7. What is the server actually doing?                 curl -v
</code></pre>
<p>Most people start at 5 and work backwards in a panic. Starting at 1 takes ninety seconds and eliminates half the possibilities.</p>
<h2 id="1-interfaces-and-addresses">1. Interfaces and addresses</h2>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -br</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> addr</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -br</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> link</span></span>
<span class="line"></span></code></pre>
<p><code>-br</code> is "brief," and it's the flag that makes <code>ip</code> pleasant to use. Compare:</p>
<pre data-lang-none><code>lo               UNKNOWN        127.0.0.1/8
eth0             UP             192.0.2.2/24
</code></pre>
<p>Two lines instead of twenty. <code>ip -br link</code> shows the same interfaces with MAC addresses and state.</p>
<p>What to look for: is the interface <strong>UP</strong>, does it have the address you expect, is the prefix length (<code>/24</code>) right. A wrong prefix length is a classic — the host thinks half the network is local and never sends those packets to the gateway.</p>
<p><code>ifconfig</code> does a similar job and is from the deprecated <code>net-tools</code> package. It's often not installed. Use <code>ip</code>.</p>
<h2 id="2-routing-%E2%80%94-the-one-command">2. Routing — the one command</h2>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> route</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> get</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 8.8.8.8</span></span>
<span class="line"></span></code></pre>
<pre data-lang-none><code>8.8.8.8 via 192.0.2.1 dev eth0 src 192.0.2.2 uid 0
    cache
</code></pre>
<p>This is the best routing command on Linux and it's underused. It doesn't dump the table for you to interpret — it asks the kernel to <em>make the actual decision</em> and report it. You get:</p>
<ul>
<li><strong><code>via 192.0.2.1</code></strong> — the next hop it will use</li>
<li><strong><code>dev eth0</code></strong> — the interface it will leave by</li>
<li><strong><code>src 192.0.2.2</code></strong> — <strong>the source IP it will use</strong>, which is the one your firewall rules on the far end need to allow</li>
</ul>
<p>That source address is the part people miss. On a multi-homed box, "which IP do we appear to come from" is not a guess — this command tells you.</p>
<p>Full table if you need it:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> route</span></span>
<span class="line"></span></code></pre>
<p>Policy routing and multiple tables are part 6.</p>
<h2 id="3-ping-%E2%80%94-and-its-limits">3. ping — and its limits</h2>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ping</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -c</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 5</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 192.0.2.1</span></span>
<span class="line"></span></code></pre>
<p><code>ping</code> sends ICMP echo requests. <code>-c 5</code> stops after five, which you want — otherwise it runs forever.</p>
<p>Read the summary line: <code>min/avg/max/mdev</code>. A tight spread means a stable path. <code>mdev</code> (mean deviation) high means jitter, which means queueing.</p>
<p><strong>The critical caveat, and it's the most common junior mistake:</strong> a failed ping does <strong>not</strong> mean the host is down.</p>
<p>Plenty of hosts and networks drop ICMP by policy. Cloud security groups routinely block it by default. A host can be perfectly healthy, serving traffic on port 443, and completely silent to <code>ping</code>.</p>
<p>So: <strong>ping succeeding is useful information. Ping failing is almost none.</strong> Never report "the host is down" based on ping alone. Test the actual port instead — step 5.</p>
<p>Useful variants:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ping</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -c</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 10</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0.2</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> host</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">      # faster interval, better sample</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ping</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -M</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> do</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1472</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> host</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">     # don't fragment, 1472+28 = 1500 — MTU testing</span></span>
<span class="line"></span></code></pre>
<p>Flags differ between the <code>iputils</code> and BSD implementations. Check <code>man ping</code> on your box.</p>
<h2 id="4-traceroute-%E2%80%94-and-why-it-lies">4. traceroute — and why it lies</h2>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">traceroute</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 8.8.8.8</span></span>
<span class="line"></span></code></pre>
<p>The mechanism, because it explains the weird output: traceroute sends packets with TTL=1, then TTL=2, then 3. Each router that decrements TTL to zero discards the packet and returns an ICMP "time exceeded" — revealing its address. Increment until you reach the destination.</p>
<p>Two things that confuse everyone:</p>
<p><strong>Stars (<code>* * *</code>) do not mean a broken hop.</strong> They mean that router chose not to send an ICMP reply. Many are configured that way. Traffic still flows straight through. A hop of stars in the middle with the destination reachable at the end is completely normal.</p>
<p><strong>Loss at an intermediate hop is usually fake.</strong> Routers deprioritise generating ICMP for their own address — it's handled by a slow control-plane CPU, not the forwarding hardware. A router showing 40% "loss" may be forwarding your real traffic flawlessly. <strong>Only loss at the final hop, or loss that persists from a hop through every hop after it, is real.</strong></p>
<p>I have watched engineers escalate to a transit provider over hop-4 ICMP rate limiting. Know this and you won't be that person.</p>
<p>By default traceroute uses UDP to high ports, which firewalls often block. TCP mode usually gets further because it looks like real traffic:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">traceroute</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -T</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -p</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 443</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> example.com</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">     # may need root</span></span>
<span class="line"></span></code></pre>
<p><code>mtr</code> combines traceroute and ping, probing continuously — better for spotting intermittent problems. <code>mtr -rwc 100 host</code> gives a report of 100 probes per hop.</p>
<h2 id="5-dns-%E2%80%94-properly">5. DNS — properly</h2>
<p>The gap that causes the most wasted hours, because DNS failures don't look like DNS failures.</p>
<h3 id="how-a-linux-host-resolves-a-name">How a Linux host resolves a name</h3>
<p><strong>Applications do not just query DNS.</strong> They call <code>getaddrinfo()</code>, which follows the Name Service Switch configuration:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">grep</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '^hosts'</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /etc/nsswitch.conf</span></span>
<span class="line"></span></code></pre>
<pre data-lang-none><code>hosts:          files dns
</code></pre>
<p>That means: check <code>/etc/hosts</code> <strong>first</strong>, then DNS. Other entries you'll meet are <code>mdns4_minimal</code>, <code>resolve</code> (systemd-resolved), and <code>myhostname</code>.</p>
<p>Then, for the DNS step:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">cat</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /etc/resolv.conf</span></span>
<span class="line"></span></code></pre>
<p>Nameservers, and a <code>search</code> domain list that gets appended to unqualified names.</p>
<p>On systemd-resolved systems, <code>/etc/resolv.conf</code> often just points at a local stub (<code>127.0.0.53</code>) and the real configuration lives elsewhere:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">resolvectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> status</span></span>
<span class="line"></span></code></pre>
<h3 id="the-distinction-that-solves-the-hard-cases">The distinction that solves the hard cases</h3>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">dig</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> +short</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> example.com</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">          # queries DNS directly</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getent</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> hosts</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> example.com</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        # goes through NSS — the same path your app uses</span></span>
<span class="line"></span></code></pre>
<p><strong><code>dig</code> bypasses <code>/etc/hosts</code> and NSS entirely.</strong> It talks to a nameserver directly.</p>
<p>So when <code>dig</code> returns the right answer but your application can't resolve the name, the problem is <em>not</em> DNS. It's <code>/etc/hosts</code>, <code>nsswitch.conf</code>, or the resolver library. That divergence between <code>dig</code> and <code>getent</code> is the fastest way to find it, and almost nobody checks both.</p>
<h3 id="dig-the-parts-you-need">dig, the parts you need</h3>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">dig</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> example.com</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                    # full answer with sections</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">dig</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> +short</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> example.com</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">             # just the addresses</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">dig</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> @8.8.8.8</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> example.com</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">           # ask a specific server — bypass yours</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">dig</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> MX</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> example.com</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                 # a specific record type</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">dig</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -x</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 192.0.2.10</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                  # reverse lookup</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">dig</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> +trace</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> example.com</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">             # follow delegation from the root</span></span>
<span class="line"></span></code></pre>
<p>Two of these earn their keep:</p>
<p><strong><code>dig @8.8.8.8</code></strong> — if your resolver gives a stale or wrong answer and a public one gives the right answer, your resolver or its cache is the problem. One command, problem localised.</p>
<p><strong><code>dig +trace</code></strong> — walks the delegation chain from the root servers down. This is how you find "the nameservers at the registrar don't match the ones in the zone," which produces intermittent failures that look like black magic.</p>
<p>Also watch the <code>Query time:</code> line in full <code>dig</code> output. A resolver taking 2 seconds is your latency problem, and no amount of TCP tuning will help.</p>
<p><code>host</code> and <code>nslookup</code> do similar jobs. <code>dig</code> gives more detail and is what you'll see in every serious runbook. It ships in <code>dnsutils</code> or <code>bind-utils</code> depending on distro.</p>
<h2 id="6-is-the-port-actually-open">6. Is the port actually open?</h2>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nc</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -zv</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 192.0.2.10</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 443</span></span>
<span class="line"></span></code></pre>
<ul>
<li><code>-z</code> — scan only, don't send data</li>
<li><code>-v</code> — tell me the result</li>
</ul>
<p>Bound the wait, or a filtered port hangs:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nc</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -zv</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -w</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 3</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 192.0.2.10</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 443</span></span>
<span class="line"></span></code></pre>
<p><strong>This, not ping, is how you test whether a service is reachable.</strong> It tests the exact thing that matters: can a TCP connection be established to that port.</p>
<p>Read the failure mode, because it tells you <em>why</em>:</p>
<ul>
<li><strong>"Connection refused", instantly</strong> — the packet reached the host and nothing is listening. The path is fine, the service is down or bound to the wrong address.</li>
<li><strong>Hangs, then times out</strong> — something is silently dropping. A firewall with a DROP rule, a security group, or the host is unreachable. See part 4 for confirming which.</li>
</ul>
<p>Fast failure and slow failure are different diagnoses. That distinction alone will save you hours.</p>
<p>A range:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nc</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -zv</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -w</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 2</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 192.0.2.10</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 20-25</span></span>
<span class="line"></span></code></pre>
<p>UDP (<code>-u</code>) is much less reliable — no handshake means no confirmation, so "success" often means "no error came back." Don't trust a UDP scan result the way you trust a TCP one.</p>
<p><strong><code>nc</code> implementations differ.</strong> OpenBSD netcat, traditional netcat, and Ncat (from nmap) have overlapping but not identical flags. Check <code>nc -h</code> on the box. If <code>nc</code> isn't installed, <code>curl -v telnet://host:port</code> works as a fallback, and <code>socat</code> is the more powerful sibling worth knowing about.</p>
<h2 id="7-which-process-owns-this-port">7. Which process owns this port?</h2>
<p>Three ways, all worth knowing:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -lntp</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                # the primary tool — part 2 covers it fully</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">lsof</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> :443</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">            # everything using port 443</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">lsof</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -P</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">           # all network connections, unresolved</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">fuser</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 443/tcp</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">           # just the PID</span></span>
<span class="line"></span></code></pre>
<p><code>lsof -i</code> is genuinely useful because it thinks in <em>files</em>, so it also shows you which files that process has open — often the fastest way to find a config file a service actually loaded. Use <code>-P</code> and <code>-n</code> to skip port-name and hostname resolution.</p>
<p>And when you need to see what a running process is actually doing at the syscall level:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">strace</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -e</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> trace=network</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -p</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">pi</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">d</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span></span>
<span class="line"></span></code></pre>
<p>You'll see the <code>socket()</code>, <code>connect()</code>, <code>accept()</code>, <code>sendto()</code> calls in real time, with their return values. This is how you answer "is it even <em>trying</em> to connect, and where?" — a question no packet capture answers, because the packet may never be sent.</p>
<p><code>strace</code> pauses the traced process on every syscall, so it is genuinely slow. Fine on a struggling service, dangerous on a hot one. Use it deliberately.</p>
<h2 id="8-tls">8. TLS</h2>
<p>Connection works, HTTPS doesn't. Look at the TLS layer directly:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">openssl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> s_client</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -connect</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> example.com:443</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -servername</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> example.com</span></span>
<span class="line"></span></code></pre>
<p><code>-servername</code> sets SNI. <strong>Leave it off and you'll often get the wrong certificate</strong>, because one IP serves many sites and the server needs the name to pick. Then you spend twenty minutes confused about a certificate mismatch you created yourself.</p>
<p>What to read in the output: the certificate chain, <code>Verify return code</code> at the bottom (<code>0 (ok)</code> is what you want), and the negotiated protocol and cipher.</p>
<p>Certificate dates specifically:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">echo</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> openssl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> s_client</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -connect</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> example.com:443</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -servername</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> example.com</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> 2></span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/dev/null</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> openssl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> x509</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -noout</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -dates</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -subject</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -issuer</span></span>
<span class="line"></span></code></pre>
<p>Expired certificates cause a startling proportion of "the network is broken" incidents.</p>
<p>Quicker, for the common case:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -vI</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> https://example.com/</span></span>
<span class="line"></span></code></pre>
<p><code>-v</code> shows the TLS handshake and certificate summary, <code>-I</code> fetches headers only. If <code>curl</code> fails but <code>nc -zv</code> to port 443 succeeds, the TCP layer is fine and your problem is TLS or HTTP — not the network.</p>
<h2 id="9-bandwidth">9. Bandwidth</h2>
<p><code>ping</code> measures latency. It tells you nothing about throughput. For that:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># On the server</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">iperf3</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># On the client</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">iperf3</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -c</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">server-i</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">p</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">iperf3</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -c</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">server-i</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">p</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -R</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">          # reverse — test the other direction</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">iperf3</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -c</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">server-i</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">p</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -P</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 4</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        # 4 parallel streams</span></span>
<span class="line"></span></code></pre>
<p>Two things worth knowing:</p>
<p><strong>Test both directions.</strong> Asymmetric performance is common and single-direction tests miss it entirely.</p>
<p><strong>A single TCP stream often can't fill a fast long-distance link.</strong> That's the window-over-RTT ceiling from part 5, not a broken network. If one stream gives you 50 Mbit/s and four parallel streams give you 200, you're seeing a per-connection window limit, not a bandwidth limit. Diagnosing that as "the link is slow" is a classic mistake.</p>
<p><code>iperf3</code> needs to be installed on both ends and it needs a port open between them.</p>
<h2 id="putting-it-together">Putting it together</h2>
<p>"The app can't reach the database." Ninety seconds:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -br</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> addr</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                          # do I have an address?</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> route</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> get</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 10.0.0.50</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">               # do I have a route, and from which source IP?</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getent</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> hosts</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> db.internal</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">             # does the name resolve the way the app sees it?</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">dig</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> +short</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> db.internal</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">               # and does DNS itself agree?</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nc</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -zv</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -w</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 3</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 10.0.0.50</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 5432</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">           # is the port actually open?</span></span>
<span class="line"></span></code></pre>
<p>Five commands and you'll usually know. And crucially, you'll know <em>which layer</em> to escalate to instead of saying "the network is broken."</p>
<p>Then read the failure:</p>
<ul>
<li><strong>No route</strong> → routing or interface configuration. Part 6.</li>
<li><strong><code>getent</code> fails but <code>dig</code> works</strong> → NSS, <code>/etc/hosts</code>, or resolver config. Not DNS.</li>
<li><strong>Both fail</strong> → DNS. Try <code>dig @&lt;other-server&gt;</code> to see if it's your resolver.</li>
<li><strong><code>nc</code> refused instantly</strong> → reached the host, nothing listening. Check the service and its bind address with <code>ss -lntp</code> (part 2).</li>
<li><strong><code>nc</code> hangs</strong> → something is dropping silently. Firewall. Part 4 proves it.</li>
</ul>
<h2 id="what-did-we-just-build">What did we just build?</h2>
<p>The floor. Seven questions, in an order, each with one command and a way to read the answer.</p>
<p>The three things worth carrying out of this post:</p>
<ol>
<li><strong>Ping failing means almost nothing.</strong> Test the port, not ICMP.</li>
<li><strong><code>dig</code> and <code>getent</code> answer different questions.</strong> When they disagree, you've found the bug.</li>
<li><strong>Fast failure and slow failure are different diagnoses.</strong> Refused means it arrived. Timeout means it didn't.</li>
</ol>
<p>Part 1 goes under the hood: what a socket really is, and the seven-stop path a packet takes from the wire to your application.</p>
<p>Primary sources: <code>man ip</code>, <code>man dig</code>, <code>man nc</code>, <code>man ping</code>, <code>man traceroute</code>, <code>man nsswitch.conf</code>, <code>man getaddrinfo</code>.</p>
<hr>
<p>Compiled by AI. Proofread by caffeine. ☕</p>]]></content:encoded>
  </item>
  <item>
    <title>nginx under load: workers, connections, and the kernel underneath</title>
    <link>https://justmeandlinux.com/nginx-under-load-workers-connections-and-the-kernel-underneath/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/nginx-under-load-workers-connections-and-the-kernel-underneath/</guid>
    <pubDate>Sun, 09 Aug 2026 14:35:45 GMT</pubDate>
    <category>Linux</category>
    <description>Worker processes, worker_connections, file descriptors, and the two kernel queues in front of them - plus the order to check things in when someone says nginx is slow.</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<p>Someone says "nginx is slow." You have a terminal and no idea whether the problem is nginx, the kernel underneath it, or the thing nginx is proxying to. This is the reference I wanted when that happened to me: the mental model, the arithmetic that sets your ceiling, and the order to check things in so you stop guessing.</p>
<p>At the end I audit a real reverse proxy on my own box and find three things wrong with it.</p>
<h2 id="where-this-design-came-from">Where this design came from</h2>
<p>nginx exists because of one specific problem, and knowing it makes every directive below make sense.</p>
<p>In 1999 Dan Kegel wrote up <strong>the C10K problem</strong>: how do you serve ten thousand concurrent connections on a single box? At the time the answer was "you don't," and the reason was architectural. Apache's classic multi-processing modules gave you <strong>one process per connection</strong> (<code>prefork</code>) or <strong>one thread per connection</strong> (<code>worker</code>). That model is easy to reason about and completely fine when connections are few and short. But the cost scales with the number of <strong>open</strong> connections, not with the amount of <strong>work</strong> — and on the public internet most connections are idle most of the time, waiting on a slow client, a keepalive timeout, or a dial-up link.</p>
<p>Ten thousand connections meant ten thousand processes or threads. Each one carries its own stack, its own scheduler entry, and its own memory footprint measured in megabytes rather than kilobytes. Long before you run out of CPU you run out of RAM, and the scheduler spends its time context-switching between threads that have nothing to do.</p>
<p>Igor Sysoev inverted it. nginx runs a <strong>fixed, small number of processes</strong> — one per core — and each one runs an event loop over thousands of sockets. Cost now scales with <strong>active work</strong> instead of open connections. Ten thousand idle keepalive connections are a few megabytes and no scheduler pressure at all, because an idle connection is just a file descriptor in an <code>epoll</code> set, not a thread waiting to be woken.</p>
<p>Apache closed a lot of this gap with the <code>event</code> MPM in 2.4, which hands idle keepalive connections off to a small dedicated pool instead of pinning a thread to each. It is a genuine improvement and the comparison is no longer the massacre it was in 2004. But a request <strong>actually in flight</strong> still occupies a worker thread, so the shape of the trade-off is unchanged: Apache's model is per-request-thread with an event-driven waiting room bolted on, nginx's is event-driven throughout.</p>
<p>The practical consequence, and the reason this matters for the rest of the article: nginx is cheap at holding connections and expensive at blocking. That is exactly the profile you want in a reverse proxy or TLS terminator sitting in front of an application server that <em>does</em> use a thread-per-request model — nginx absorbs ten thousand slow clients so your backend only ever sees the small number of requests that are genuinely doing work.</p>
<p>It is also why <code>worker_connections</code> exists as a directive at all. You are not capping a process pool. You are <strong>pre-sizing an event loop</strong>, telling one worker how many descriptors it should be prepared to hold at once.</p>
<h2 id="the-model-you-need-in-your-head">The model you need in your head</h2>
<p>nginx runs one <strong>master</strong> process and N <strong>worker</strong> processes.</p>
<p>The master does almost nothing interesting. It reads the config, binds the listening sockets, spawns workers, and handles signals. It does not serve traffic. Notably, <strong>the master opens the listening socket and the workers inherit it</strong> — which is why a reload can swap workers without dropping the socket.</p>
<p>Each worker is <strong>single-threaded and event-driven</strong>. It is not one-thread-per-connection. A worker sits in <code>epoll_wait()</code> holding thousands of connections, wakes up when any of them has something to say, does a small amount of non-blocking work, and goes back to sleep. This is why nginx holds 10,000 idle connections on almost no memory, and why <strong>one blocking operation in a worker stalls every connection that worker owns</strong>.</p>
<p>That last point is the source of most surprising nginx behaviour. A slow disk read, a synchronous DNS lookup, a blocking third-party module — while that happens, thousands of unrelated requests sit still. <code>aio threads</code> exists to push file I/O onto a thread pool for exactly this reason.</p>
<p>Three consequences worth internalising:</p>
<ul>
<li><strong>CPU work parallelises across workers, not within one.</strong> One worker = one core, maximum.</li>
<li><strong>A single expensive request cannot be preempted.</strong> Event loops are cooperative.</li>
<li><strong>Load balance between workers is the kernel's job, not nginx's.</strong> More on that under reuseport.</li>
</ul>
<h2 id="the-arithmetic-that-sets-your-ceiling">The arithmetic that sets your ceiling</h2>
<p>Two directives decide the maximum number of connections nginx will hold:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="nginx"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">worker_processes </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> auto;   </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># one worker per CPU core</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">events</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    worker_connections </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1024</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;   </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># per worker, and this default is the trap</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span></code></pre>
<p>The ceiling is the product:</p>
<pre data-lang-none><code>max connections = worker_processes × worker_connections
</code></pre>
<p><code>worker_connections</code> counts <strong>every</strong> connection a worker owns, not just the client-facing ones. This is where the arithmetic usually goes wrong, because "two per client" gets repeated as though it were a law, and it is only the common case.</p>
<p>What one client request actually costs:</p>
<table>
<thead>
<tr>
<th>what you are doing</th>
<th>connections held</th>
</tr>
</thead>
<tbody>
<tr>
<td>static file from disk or page cache</td>
<td><strong>1</strong> — the client</td>
</tr>
<tr>
<td>reverse proxy to an upstream</td>
<td><strong>2</strong> — client + upstream</td>
</tr>
<tr>
<td>proxy where the upstream is a hostname needing resolution</td>
<td><strong>+1</strong> while the <code>resolver</code> query is in flight</td>
</tr>
<tr>
<td>proxy behind <code>auth_request</code></td>
<td><strong>+1</strong> — the auth subrequest is its own upstream connection</td>
</tr>
<tr>
<td>proxy with cache revalidation in progress</td>
<td><strong>+1</strong></td>
</tr>
<tr>
<td><code>mirror</code> to a shadow environment</td>
<td><strong>+1 per mirror</strong></td>
</tr>
</tbody>
</table>
<p>So a fairly ordinary setup — <code>auth_request</code> in front of a <code>proxy_pass</code> to a resolved hostname — can hold <strong>four</strong> connections for one user, not two. WebSockets and gRPC streams are still two, but they hold both ends for the entire life of the session rather than milliseconds, which hurts a ceiling far more than a higher count would.</p>
<p>The general form:</p>
<pre data-lang-none><code>max clients ≈ (worker_processes × worker_connections) / connections_per_client
</code></pre>
<p>Use 2 as a floor for any proxy, and count your own middleware before trusting it. The number you want is whatever your busiest path actually opens.</p>
<p>The default <code>worker_connections 1024</code> has shipped for years and is far too low for anything under real load. On a 16-core box that is 16,384 total, or roughly 8,000 proxied clients — and people are routinely surprised by that number because nobody ever changed a default they never read.</p>
<p>When you hit it, nginx tells you plainly in <code>error.log</code>:</p>
<pre data-lang-none><code>[alert] 1234#0: 768 worker_connections are not enough
</code></pre>
<p>That message is a gift. Most limits fail with something far more cryptic.</p>
<h2 id="file-descriptors-the-limit-that-bites-first">File descriptors: the limit that bites first</h2>
<p>Every connection is a file descriptor. Every open log file, every cached file handle, every upstream socket. So the fd limit has to be <strong>larger</strong> than your connection ceiling, and it is the constraint people forget because it lives outside nginx.</p>
<p>There are three places a limit can come from, and they override each other in this order:</p>
<ol>
<li><strong>The kernel's global cap</strong> — <code>fs.file-max</code>. Rarely the problem on modern systems.</li>
<li><strong>The process limit</strong> — <code>RLIMIT_NOFILE</code>. This is the one that bites.</li>
<li><strong>nginx's own directive</strong> — <code>worker_rlimit_nofile</code>, which calls <code>setrlimit()</code> for workers.</li>
</ol>
<p>Where <code>RLIMIT_NOFILE</code> comes from depends on how nginx starts:</p>
<ul>
<li><strong>systemd</strong> ignores <code>/etc/security/limits.conf</code> entirely. It uses <code>LimitNOFILE=</code> in the unit.<br>
This is the single most common cause of "I raised the limit and nothing changed" — the file you edited was never consulted.</li>
<li><strong>Docker</strong> takes it from the daemon's default ulimits or <code>--ulimit nofile=</code>.</li>
<li><strong><code>worker_rlimit_nofile</code></strong> in <code>nginx.conf</code> overrides both for workers, up to the hard limit.</li>
</ul>
<p>Never trust the config. Ask the running process:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># what the worker actually has, not what you configured</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">cat</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /proc/</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">$(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">pgrep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'nginx: worker'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> head</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/limits</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'open files'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># how many it is using right now</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ls</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /proc/</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">$(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">pgrep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'nginx: worker'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> head</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/fd</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> wc</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -l</span></span>
<span class="line"></span></code></pre>
<p>The failure mode is unmistakable once you know it:</p>
<pre data-lang-none><code>[crit] accept4() failed (24: Too many open files)
</code></pre>
<p>Errno 24 is <code>EMFILE</code>. nginx is alive, healthy, and refusing connections. A rule of thumb that has never let me down: <strong>set <code>worker_rlimit_nofile</code> to at least twice <code>worker_connections</code></strong>, and make sure the hard limit is above that.</p>
<h2 id="what-the-kernel-does-before-nginx-sees-anything">What the kernel does before nginx sees anything</h2>
<p>A connection passes through two kernel queues before a worker ever touches it. If you tune nginx without understanding these, you tune the wrong thing.</p>
<p><strong>1. The SYN queue</strong> holds half-open connections between <code>SYN</code> and the final <code>ACK</code>. Sized by <code>net.ipv4.tcp_max_syn_backlog</code>. Overflow here shows up as SYN cookies or dropped handshakes.</p>
<p><strong>2. The accept queue</strong> holds fully established connections waiting for the application to call <code>accept()</code>. Sized by <code>min(listen backlog, net.core.somaxconn)</code>.</p>
<p>That <code>min()</code> is where people get caught. nginx's <code>listen ... backlog=N</code> is capped silently by <code>somaxconn</code>. Ask for 65535 with <code>somaxconn</code> at 4096 and you get 4096, with no warning.</p>
<p>You can see both queues directly:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># on a LISTEN socket: Recv-Q = connections waiting to be accepted</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#                     Send-Q = the accept queue's maximum</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -lnt</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># has the accept queue ever overflowed? this counter is the smoking gun</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nstat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -az</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -E</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'ListenOverflows|ListenDrops'</span></span>
<span class="line"></span></code></pre>
<p><strong>A non-zero and climbing <code>ListenOverflows</code> means the kernel completed handshakes that nginx never accepted.</strong> The client saw a connection succeed and then hang. That is nginx being too slow to accept — workers busy, blocked, or too few — not a network problem, and no amount of <code>somaxconn</code> will fix the cause. It only buys you a bigger waiting room.</p>
<p>The other kernel knobs that matter under real load:</p>
<table>
<thead>
<tr>
<th>sysctl</th>
<th>what it governs</th>
<th>when to touch it</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>net.core.somaxconn</code></td>
<td>accept queue cap</td>
<td>connection spikes, <code>ListenOverflows</code> climbing</td>
</tr>
<tr>
<td><code>net.ipv4.tcp_max_syn_backlog</code></td>
<td>SYN queue</td>
<td>handshake floods; keep ≥ somaxconn</td>
</tr>
<tr>
<td><code>net.core.netdev_max_backlog</code></td>
<td>per-CPU packet ingress queue</td>
<td>high packets/sec, 10G+ NICs</td>
</tr>
<tr>
<td><code>net.ipv4.ip_local_port_range</code></td>
<td>ephemeral ports for outbound</td>
<td><strong>reverse proxies — see below</strong></td>
</tr>
<tr>
<td><code>net.ipv4.tcp_tw_reuse</code></td>
<td>reuse TIME_WAIT for outbound</td>
<td>port exhaustion; <code>2</code> = loopback only, the modern default</td>
</tr>
</tbody>
</table>
<h2 id="soreuseport">SO_REUSEPORT</h2>
<p>By default the master creates <strong>one</strong> listening socket and every worker calls <code>accept()</code> on it. The kernel wakes workers to compete for each new connection, and the distribution is uneven — under high connection rates you get one hot worker and fifteen bored ones.</p>
<p>One word fixes it:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="nginx"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">listen </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">443</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ssl reuseport;</span></span>
<span class="line"></span></code></pre>
<p>Now <strong>each worker gets its own listening socket and its own accept queue</strong>, and the kernel hashes incoming connections across them. No contention, far better balance across cores. On connection-heavy workloads this is one of the largest single-line wins available.</p>
<p>Two things to know before you paste it in:</p>
<ul>
<li>Put <code>reuseport</code> on <strong>one</strong> <code>listen</code> directive per address:port. Repeating it across server<br>
blocks for the same socket is a config error.</li>
<li><strong>Reloads become slightly lossy.</strong> Each worker owns a private accept queue; when workers are<br>
replaced, connections sitting in an old worker's queue can be dropped rather than handed over. For most sites this is invisible. If you reload constantly under heavy load, know the trade.</li>
</ul>
<p>Related, and usually best left alone: <code>accept_mutex</code> has defaulted to <code>off</code> since nginx 1.11.3, and <code>reuseport</code> makes it irrelevant. <code>multi_accept on</code> tells a worker to drain all pending connections per wakeup — it can raise throughput and can also raise latency variance. Measure, don't assume.</p>
<h2 id="what-to-expect-in-orders-of-magnitude">What to expect, in orders of magnitude</h2>
<p>Precise numbers from someone else's hardware are worthless, but orders of magnitude stop you chasing the wrong bottleneck. Modern x86, per core:</p>
<ul>
<li><strong>Static file from page cache, keepalive on:</strong> tens of thousands of requests/sec per core. nginx<br>
is rarely the limit here; your NIC or the kernel's packet path is.</li>
<li><strong>New TCP connection per request (no keepalive):</strong> an order of magnitude worse. Connection setup<br>
and teardown dominate. This is the single biggest self-inflicted wound in benchmarks.</li>
<li><strong>TLS handshakes:</strong> the expensive part, and the algorithm matters enormously. <strong>ECDSA P-256 is<br>
several times cheaper than RSA-2048</strong> per handshake. Session resumption skips the asymmetric work entirely.</li>
<li><strong>Bulk TLS transfer:</strong> cheap on any CPU with AES-NI. Handshakes cost, streaming does not.</li>
</ul>
<p>The practical takeaways: if you are TLS-terminating, <strong>your handshake rate is your throughput</strong>, and enabling session resumption plus moving to an ECDSA certificate will beat every sysctl in this article. If your benchmark opens a fresh connection per request, you are measuring connection setup, not nginx.</p>
<h2 id="triage-the-order-that-finds-it-fastest">Triage: the order that finds it fastest</h2>
<p>Work outside in. Each step rules out a layer.</p>
<p><strong>Step 0 — make nginx observable.</strong> If you have not done this, do it before you need it:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="nginx"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># a timing-aware log format: this single change ends most "is it nginx or the app?" arguments</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">log_format </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">timing </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'$</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">remote_addr</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> $</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">status</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> $</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">request_time</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">                  'upstream=$</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">upstream_response_time</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> connect=$</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">upstream_connect_time</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">                  'header=$</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">upstream_header_time</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "$</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">request</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># and the built-in counters</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">location</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF"> /nginx_status </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    stub_status</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    allow </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">127.0.0.1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    deny </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">all</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span></code></pre>
<p><code>$request_time</code> is the whole request. <code>$upstream_response_time</code> is what your backend took. <strong>If those two are close, the problem is not nginx.</strong> That is usually the entire investigation.</p>
<p><strong>Step 1 — how many connections do you actually have?</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                                          # totals, incl. timewait</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -tan</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> state</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> established</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '( sport = :443 )'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> wc</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -l</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> localhost/nginx_status</span></span>
<span class="line"></span></code></pre>
<p><code>stub_status</code> returns something like <code>Active connections: 291</code> and a line of three numbers: <code>accepts handled requests</code>.</p>
<p><strong>If <code>accepts</code> and <code>handled</code> differ, nginx dropped connections</strong> — it hit <code>worker_connections</code> or ran out of file descriptors. Those two numbers should be identical forever. It is the fastest single check on the whole box, and almost nobody looks at it.</p>
<p><strong>Step 2 — are you hitting a limit?</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">grep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -c</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'worker_connections are not enough'</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /var/log/nginx/error.log</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">grep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -c</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'Too many open files'</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /var/log/nginx/error.log</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">cat</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /proc/</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">$(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">pgrep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'nginx: worker'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> head</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/limits</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'open files'</span></span>
<span class="line"></span></code></pre>
<p><strong>Step 3 — is the kernel dropping before nginx?</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nstat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -az</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -E</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'ListenOverflows|ListenDrops'</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -lnt</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                      # Recv-Q climbing on a LISTEN row = you are not accepting fast enough</span></span>
<span class="line"></span></code></pre>
<p><strong>Step 4 — are you CPU bound, and is it one worker or all of them?</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">top</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -H</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">pgrep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -d,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'nginx: worker'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span></code></pre>
<p>All workers pegged → genuinely CPU bound, likely TLS. <strong>One worker pegged while others idle → uneven distribution: reach for <code>reuseport</code>.</strong> That asymmetry is the tell.</p>
<p><strong>Step 5 — the upstream side.</strong> Covered next, because it deserves its own section.</p>
<h2 id="the-upstream-side-where-reverse-proxies-die">The upstream side: where reverse proxies die</h2>
<p>Every outbound connection to an upstream consumes an <strong>ephemeral port</strong> from <code>net.ipv4.ip_local_port_range</code>. The usual range is 32768–60999, which is about <strong>28,000 ports</strong>.</p>
<p>A port is not reusable immediately after close — it sits in <code>TIME_WAIT</code> for 60 seconds. So a proxy opening a fresh upstream connection per request tops out around <strong>28,000 / 60 ≈ 470 connections per second</strong> to a single upstream <code>(ip, port)</code> pair, no matter how much CPU you have.</p>
<p>Then you get this, and it looks like a mystery:</p>
<pre data-lang-none><code>connect() to 10.0.0.5:8080 failed (99: Cannot assign requested address)
</code></pre>
<p>That is not a network error. That is ephemeral port exhaustion.</p>
<p>The fix is not a sysctl. It is <strong>keepalive to your upstream</strong>, and it needs three lines that must all be present — the two <code>proxy_*</code> lines are the ones everybody forgets, and without them the <code>keepalive</code> directive silently does nothing:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="nginx"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">upstream</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> backend </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    server</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> 10.0.0.5:8080;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    keepalive </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">64</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;               </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># persistent connections held per worker</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">server</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    location</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> / </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        proxy_pass </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">http://backend;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        proxy_http_version </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1.1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;          </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># required — 1.0 cannot keep alive</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        proxy_set_header </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Connection </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">""</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;  </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># required — strips the inbound "close"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span></code></pre>
<p>Widening <code>ip_local_port_range</code> and setting <code>tcp_tw_reuse</code> treat the symptom. Connection reuse removes the problem.</p>
<p><strong>One more ceiling if you are behind Docker or any NAT:</strong> every connection consumes a conntrack entry. When <code>nf_conntrack_count</code> approaches <code>nf_conntrack_max</code>, the kernel starts dropping packets and logs <code>nf_conntrack: table full, dropping packet</code>. It presents as random, unexplained connection failures under load.</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sysctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> net.netfilter.nf_conntrack_max</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> net.netfilter.nf_conntrack_count</span></span>
<span class="line"></span></code></pre>
<h2 id="what-not-to-tune">What not to tune</h2>
<ul>
<li><strong><code>tcp_tw_recycle</code></strong> — do not look for it. It broke clients behind NAT and was <strong>removed from<br>
Linux in 4.12</strong>. Any guide still recommending it was written for a kernel you are not running.</li>
<li><strong>Raising <code>somaxconn</code> to "fix" overflows</strong> — it enlarges the waiting room. If <code>ListenOverflows</code><br>
climbs, workers are not accepting fast enough; find out why.</li>
<li><strong><code>worker_processes</code> above your core count</strong> — workers then compete for the same cores and you<br>
add context switching for nothing. <code>auto</code> is right almost always.</li>
<li><strong>Copy-pasted sysctl blocks</strong> from blog posts, applied wholesale. Every knob in this article<br>
fixes a specific, observable symptom. If you cannot name the counter that made you change it, do not change it.</li>
</ul>
<h2 id="worked-example-auditing-a-live-reverse-proxy">Worked example: auditing a live reverse proxy</h2>
<p>Theory is cheap. Here is my own <code>nginx-proxy</code> container, which has been running untouched for five months, on a 16-core host.</p>
<pre data-lang-none><code># nginx version: nginx/1.29.2
worker_processes  auto;
events { worker_connections  1024; }
</code></pre>
<p><code>auto</code> on 16 cores gives 16 workers, which I confirmed rather than assumed:</p>
<pre data-lang-none><code>$ docker top nginx-proxy -o pid,args
nginx: master process nginx -g daemon off;
nginx: worker process        ← ×16
</code></pre>
<p><strong>Finding 1 — the connection ceiling is a stock default nobody chose.</strong> 16 × 1024 = <strong>16,384</strong> connections; as a reverse proxy that is roughly <strong>8,000 concurrent clients</strong>. Not a crisis for a homelab, but it is the number I would hit first under load, and it is there by accident rather than decision.</p>
<p><strong>Finding 2 — file descriptors are not the constraint, by luck.</strong></p>
<pre data-lang-none><code>$ grep 'Max open files' /proc/&lt;worker&gt;/limits
Max open files    1048576    1048576    files
$ ls /proc/&lt;worker&gt;/fd | wc -l
25
</code></pre>
<p>A million fds, 25 in use. That headroom comes from the container image's defaults, not from anything in my config — <code>worker_rlimit_nofile</code> is not set. On a systemd host with a stock <code>LimitNOFILE</code>, the same config would fail long before <code>worker_connections</code> was reached. <strong>The constraint here is <code>worker_connections</code>, which is the healthy way round</strong> — but only by accident.</p>
<p><strong>Finding 3 — nginx is unobservable.</strong> No <code>stub_status</code>, and the log format is stock <code>main</code> with no timings. I cannot answer "did nginx drop connections?" or "was it nginx or the upstream?" without changing the config first — which is exactly the wrong time to be editing config.</p>
<p>The kernel side, for contrast, is healthy:</p>
<pre data-lang-none><code>net.core.somaxconn              4096
net.ipv4.tcp_max_syn_backlog    2048
net.ipv4.ip_local_port_range    32768  60999      # 28,232 ports
net.ipv4.tcp_tw_reuse           2                 # loopback only — the modern default
nf_conntrack_max                262144            # in use: 385

TcpExtListenOverflows           0
TcpExtListenDrops               0
</code></pre>
<p>Zero overflows since boot: nothing has ever waited in the accept queue. <code>tcp_max_syn_backlog</code> (2048) sitting below <code>somaxconn</code> (4096) is worth evening out, and <code>netdev_max_backlog</code> is at the stock 1000, which only matters at packet rates I will never see here.</p>
<p><strong>And the trap I hadn't thought about until I looked.</strong> I checked the listening sockets on the host:</p>
<pre data-lang-none><code>$ ss -lntp '( sport = :80 or sport = :443 )'
LISTEN 0 4096   0.0.0.0:80    users:(("docker-proxy",...))
LISTEN 0 4096   100.96.60.8:443  users:(("tailscaled",...))
</code></pre>
<p><strong>Neither of those is nginx.</strong> Port 80 is held by <code>docker-proxy</code> and 443 by <code>tailscaled</code>. nginx's own listening socket lives inside the container's network namespace:</p>
<pre data-lang-none><code>$ docker exec nginx-proxy netstat -lntp
tcp  0  0 0.0.0.0:80   LISTEN   1/nginx: master pro
</code></pre>
<p>So every accept-queue number I could have read on the host describes <strong>docker-proxy's</strong> queue, not nginx's. If you run nginx in a container and debug queues from the host, you are measuring the wrong socket. Use <code>nsenter</code> into the container's netns, or run the tools inside it.</p>
<p>That one is worth the price of admission. It is invisible until it wastes an afternoon.</p>
<h2 id="the-one-screen-checklist">The one-screen checklist</h2>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 1. what is the ceiling?</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nginx</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -T</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -E</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'worker_processes|worker_connections'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 2. did nginx drop anything? (accepts must equal handled, forever)</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> localhost/nginx_status</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 3. what limit does the worker really have?</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">cat</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /proc/</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">$(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">pgrep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'nginx: worker'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">head</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/limits</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'open files'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 4. did the kernel drop before nginx?</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nstat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -az</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -E</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'ListenOverflows|ListenDrops'</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -lnt</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 5. one hot worker, or all of them?</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">top</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -H</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">pgrep</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -d,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'nginx: worker'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 6. running out of ephemeral ports?</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ss</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">grep</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'Cannot assign requested address'</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /var/log/nginx/error.log</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 7. nginx or the backend?  ($request_time vs $upstream_response_time)</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tail</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /var/log/nginx/access.log</span></span>
<span class="line"></span></code></pre>
<p>If you take one thing: <strong><code>accepts</code> must equal <code>handled</code> in <code>stub_status</code>.</strong> The moment they diverge, nginx is refusing work, and you have exactly two suspects — <code>worker_connections</code> and file descriptors.</p>]]></content:encoded>
  </item>
  <item>
    <title>witr: Trace Any Process Back to Why It&apos;s Running</title>
    <link>https://justmeandlinux.com/witr-trace-any-process-back-to-why-its-running/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/witr-trace-any-process-back-to-why-its-running/</guid>
    <pubDate>Sat, 08 Aug 2026 10:01:05 GMT</pubDate>
    <category>Apps</category>
    <description>You know the feeling. ps aux scrolls past. Something is listening on port 5000 and you have no idea what put it there. You start the ritual: lsof -i :5000 , then ps -p &amp;lt;pid&amp;gt; , then systemctl status , then docker ps , stitching the story together in your…</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>You know the feeling. <code>ps aux</code> scrolls past. Something is listening on port 5000 and you have no idea what put it there. You start the ritual: <code>lsof -i :5000</code>, then <code>ps -p &lt;pid&gt;</code>, then <code>systemctl status</code>, then <code>docker ps</code>, stitching the story together in your head. <code>witr</code> does that stitching for you.</p>
<h2 id="what-it-is">What it is</h2>
<p><code>witr</code> — short for &quot;Why Is This Running?&quot; — is a single-binary CLI (with a TUI) that traces any process, port, container, or file back to the exact chain that started it. Point it at a name, a PID, a port, or a container, and it hands you a causal ancestry chain: <code>systemd → pm2 → node</code>, the working directory, the git branch, the bound sockets. One command instead of five.</p>
<p>The tools we already have — <code>ps</code>, <code>top</code>, <code>lsof</code>, <code>ss</code>, <code>systemctl</code>, <code>docker ps</code> — are great at showing <em>what</em> is running. They leave the <em>why</em> to you. <code>witr</code> makes causality explicit.</p>
<h2 id="why-its-worth-your-time">Why it's worth your time</h2>
<p>The value isn't a prettier <code>ps</code>. It's the ancestry chain. When a process shows up and you don't recognize it, the real question is never &quot;what is this&quot; — it's &quot;who started this, and what's keeping it alive.&quot; That answer is normally spread across a supervisor, maybe a container runtime, maybe a stray SSH session, maybe cron. <code>witr</code> collapses all of that into one narrative.</p>
<p>For anyone running a self-hosted box with a dozen services layered through systemd, Docker, and PM2, this is the tool you didn't know you were manually emulating every time something misbehaved.</p>
<h2 id="hands-on">Hands on</h2>
<p>Install is a single binary, and it's already packaged nearly everywhere. On Debian sid / Ubuntu 26.04+ (and Kali, Devuan, Raspbian) it's in the official repos:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sudo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> apt</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> witr</span></span>
<span class="line"></span></code></pre>
<p>Homebrew, on macOS or Linux:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> witr</span></span>
<span class="line"></span></code></pre>
<p>Or the install script for the freshest release:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -fsSL</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> https://raw.githubusercontent.com/pranshuparmar/witr/main/install.sh</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> bash</span></span>
<span class="line"></span></code></pre>
<p>(That last one pipes a script to bash — read it first if that makes you nervous. It should.)</p>
<p>Now the good part. Ask it about a name:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">witr</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> node</span></span>
<span class="line"></span></code></pre>
<pre data-lang-none><code>Target      : node
Process     : node (pid 14233)
User        : pm2
Command     : node index.js
Started     : 2 days ago (Mon 2025-02-02 11:42:10 +05:30)

Why It Exists :
  systemd (pid 1) → pm2 (pid 5034) → node (pid 14233)

Source      : pm2
Working Dir : /opt/apps/expense-manager
Git Repo    : expense-manager (main)
Sockets     : 127.0.0.1:5001 (TCP | LISTENING)
</code></pre>
<p>That &quot;Why It Exists&quot; line is the whole pitch. It read the supervisor, walked the chain, and told you PM2 is keeping this alive — plus which repo and branch it's running from. That last detail alone has saved me a <code>cd</code> and a <code>git status</code>.</p>
<p>Chase a port instead, ancestry only:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">witr</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --port</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 5000</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --short</span></span>
<span class="line"></span></code></pre>
<pre data-lang-none><code>systemd (pid 1) → PM2 v5.3.1: God (pid 1481580) → python (pid 1482060)
</code></pre>
<p>Or get the full tree, including children:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">witr</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --pid</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 143895</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --tree</span></span>
<span class="line"></span></code></pre>
<p>Every target flag — <code>--pid</code>, <code>--port</code>, <code>--file</code>, <code>--container</code> — is repeatable and mixable. <code>--container</code> searches across Docker, Podman, nerdctl, k8s/crictl, Incus, LXC, LXD, and FreeBSD jails, so <code>witr --container redis</code> finds it regardless of runtime.</p>
<p>Two things worth knowing. First, <code>--json</code> gives you machine-readable output, and <code>witr</code> returns real exit codes (<code>0</code> clean, <code>1</code> warnings, <code>2</code> not found, <code>3</code> permission denied) — which means it slots into scripts and CI, not just interactive poking. Second, run <code>witr</code> with no arguments and you get the TUI: live tabs for processes, ports, containers, and file locks, with an ancestry side panel and the ability to send signals right from the UI.</p>
<p>The warnings deserve a mention too — it flags things like a process listening on <code>0.0.0.0</code>, running as root, holding dangerous capabilities, or showing <code>LD_PRELOAD</code> injection indicators. Not a security scanner, but a useful nudge.</p>
<h2 id="honest-verdict">Honest verdict</h2>
<p>I like this one. It's not solving an impossible problem — everything <code>witr</code> reports, you <em>could</em> dig out by hand. But &quot;could&quot; is doing a lot of work in that sentence, and the whole point is that you stop doing it by hand. The ancestry chain is genuinely the right abstraction for the question, and the tool is honest about uncertainty rather than pretending it always knows.</p>
<p>The caveats are inherent, not sloppy: process ancestry is a best-effort, real-time walk, so very short-lived processes can vanish before <code>witr</code> catches them. Detection is &quot;best effort with explicit uncertainty&quot; by design — it won't lie to you, but it also won't always have a clean answer. And realistically, some of the deeper output needs root to see everything (that <code>3</code> exit code exists for a reason).</p>
<p>Who's it for? Anyone who administers a box with more than a couple of moving parts. If your whole world is one Docker Compose file, you'll reach for it occasionally. If you're running a real self-hosted stack, it earns a spot in your PATH.</p>
<h2 id="go-try-it">Go try it</h2>
<p>Install it, then just run <code>witr</code> with no arguments and poke around the TUI for two minutes. After that, next time something's on a port you don't recognize, type <code>witr --port &lt;n&gt;</code> and watch it tell you the whole story. That's the moment it clicks.</p>
<p>Project: <a href="https://github.com/pranshuparmar/witr">github.com/pranshuparmar/witr</a> — and there's a <a href="https://pranshuparmar.github.io/witr/">browser sandbox</a> if you want to try it before installing anything.</p>
<!--kg-card-end: markdown-->]]></content:encoded>
  </item>
  <item>
    <title>Harper: The Grammar Checker That Stays on Your Machine</title>
    <link>https://justmeandlinux.com/harper-the-grammar-checker-that-stays-on-your-machine/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/harper-the-grammar-checker-that-stays-on-your-machine/</guid>
    <pubDate>Sat, 25 Jul 2026 10:01:47 GMT</pubDate>
    <category>Apps</category>
    <description>Grammarly is a privacy nightmare. LanguageTool needs 16GB of data. Harper is the offline grammar checker nobody talks about enough — and it belongs in your editor.</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Grammarly sends everything you type to their servers. LanguageTool needs a 16GB n-gram dataset just to get started. There's a third option that most people haven't heard of yet — and it's better than both for most use cases.</p>
<h2 id="what-it-is">What It Is</h2>
<p>Harper is an offline grammar checker that runs entirely on your device. No cloud round-trips, no telemetry, no subscription. It ships as a Language Server Protocol binary called <code>harper-ls</code>, which means it integrates directly into any editor that speaks LSP: Neovim, VSCode, Helix, Emacs, Zed. Written in Rust. Responds in under 10ms. Made by Automattic — the people behind WordPress — and fully open source under Apache 2.0.</p>
<h2 id="why-its-worth-your-time">Why It's Worth Your Time</h2>
<p>The competition got this wrong in two different ways.</p>
<p>Grammarly built a cloud product with surveillance-grade data collection baked in. Their business model depends on knowing what you write. They claim they don't sell your data, but that's a different thing from not using it. Every keystroke in every document goes to their servers. The round-trip latency makes revisions tedious. The suggestions lack context and are often just wrong.</p>
<p>LanguageTool went the opposite direction and built something technically powerful but impractical: the full offline version requires gigabytes of n-gram data and significant RAM just to run. It's overkill for most people and slow even when you have it set up.</p>
<p>Harper is neither. Single binary. Fraction of LanguageTool's memory footprint. Completely private.</p>
<p>What makes it genuinely interesting for developers: it doesn't just check <code>.md</code> files. It checks the English <em>inside your code comments</em> — Python docstrings, Rust doc comments, Go comments, Shell scripts, JavaScript, and about 30 more languages. Your inline documentation is fair game too, which is where most developer writing actually happens.</p>
<h2 id="hands-on">Hands On</h2>
<p><strong>Install on Debian/Ubuntu (via Cargo):</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">cargo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> harper-ls</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --locked</span></span>
<span class="line"></span></code></pre>
<p>Make sure <code>~/.cargo/bin</code> is in your <code>$PATH</code>. You may need <code>build-essential</code> on Debian-based distros.</p>
<p><strong>Arch Linux:</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sudo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> pacman</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -S</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> harper</span></span>
<span class="line"></span></code></pre>
<p><strong>Homebrew (macOS/Linux):</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> harper</span></span>
<span class="line"></span></code></pre>
<p><strong>No Rust? No problem.</strong> Pre-built binaries are available on the <a href="https://github.com/Automattic/harper/releases">GitHub releases page</a>.</p>
<p><strong>Neovim + mason.nvim:</strong> <code>harper-ls</code> is in the Mason registry — search and install it from there.</p>
<p><strong>VSCode:</strong> There's an official extension in the marketplace.</p>
<p>Once installed, Harper creates a plain-text user dictionary at <code>~/.config/harper-ls/dictionary.txt</code>. When it flags a term you use intentionally — a product name, a technical acronym, a brand — you add it once and Harper stops flagging it. No UI required, just edit the file.</p>
<p>LSP integration means code actions work natively. Hover over a flagged word and your editor offers: fix the spelling, ignore for the session, or add to the user dictionary. It catches capitalization errors, repeated words, wrong a/an usage, standard spelling mistakes. No LLM involved — these are deterministic rules, which means it's fast and predictable.</p>
<p>You can also select a dialect: American, British, Australian, Canadian, or Indian English. Useful if you write for specific audiences.</p>
<p><strong>A sample LSP config for Neovim (using <code>nvim-lspconfig</code>):</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="lua"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">require</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'lspconfig'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">harper_ls</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">setup</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  settings </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"harper-ls"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      linters </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        SpellCheck </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        RepeatedWords </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        LongSentences </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        AnA </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      },</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      diagnosticSeverity </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "hint"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      dialect </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "American"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span></code></pre>
<p>This gives you grammar hints in your editor as you type — not blocking errors, just quiet nudges.</p>
<h2 id="honest-verdict">Honest Verdict</h2>
<p>English-only for now. That's a real limitation — if you write in Arabic, French, or any other language, you'll constantly fight false positives. The team says other languages are planned, but there's no timeline.</p>
<p>The grammar rules are also narrower than Grammarly's full suite. Don't expect it to restructure awkward sentences or suggest better phrasing. Harper catches the mechanical stuff: spelling, repeated words, capitalization, a/an. That's the right scope for a tool running locally with no AI backend.</p>
<p>What it does, it does well and without friction. For anyone who writes documentation, technical blog posts, or just cares about the English in their code comments — this is exactly the kind of tool you install once and forget about until you notice it quietly preventing the same three mistakes you always make.</p>
<p>It's not a Grammarly replacement for heavy prose editing. It's a lightweight, always-on safety net that respects your privacy. That's a useful thing to have.</p>
<h2 id="go-try-it">Go Try It</h2>
<p>Install <code>harper-ls</code> and hook it into your editor. Write something. Watch the underlines appear in under 10ms. That response time alone will tell you why this thing exists.</p>
<p>→ <a href="https://writewithharper.com">writewithharper.com</a> | <a href="https://github.com/Automattic/harper">GitHub</a></p>
<!--kg-card-end: markdown-->]]></content:encoded>
  </item>
  <item>
    <title>croc: file transfer through NAT without SSH or open ports</title>
    <link>https://justmeandlinux.com/croc-peer-to-peer-file-transfer-that-actually-works-through-nat/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/croc-peer-to-peer-file-transfer-that-actually-works-through-nat/</guid>
    <pubDate>Thu, 23 Jul 2026 10:01:51 GMT</pubDate>
    <category>Apps</category>
    <description>Most file transfer tools need SSH access or open ports. croc needs neither. Send files between any two machines with a code phrase and end-to-end encryption.</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Every time I need to move a file between two machines, I open a terminal and immediately regret my life choices. <code>scp</code> requires SSH access on both ends. <code>rsync</code> does too. Spinning up a quick Python HTTP server means knowing the sender's IP and punching through a firewall. croc sidesteps all of it.</p>
<h2 id="what-it-is">What It Is</h2>
<p>croc is a command-line file transfer tool written in Go. The sender runs <code>croc send file.tar.gz</code>, gets a short code phrase back, and shares it with whoever needs the file. The recipient runs <code>croc &lt;code-phrase&gt;</code> on their end. The transfer happens — end-to-end encrypted, no port-forwarding needed, across NAT, on any platform. Windows, Linux, Mac, doesn't matter.</p>
<p>It's not magic. The trick is a relay server that handles the connection handshake, so you don't need a direct path between machines. But your content is encrypted using PAKE (Password-Authenticated Key Exchange) before it ever leaves your machine — the relay sees ciphertext, not your files.</p>
<h2 id="why-its-worth-your-time">Why It's Worth Your Time</h2>
<p>What separates croc from similar tools like magic-wormhole (which directly inspired it):</p>
<p><strong>Transfers resume.</strong> Drop a connection halfway through a 2 GB backup and it picks up where it stopped. That alone is worth the install.</p>
<p><strong>Multi-file and folder support.</strong> Send a whole directory: <code>croc send /path/to/folder</code>. Exclude what you don't want: <code>--exclude &quot;node_modules,.venv&quot;</code>.</p>
<p><strong>Self-hostable relay.</strong> This is the part I care about most for self-hosters. By default, croc uses a public relay. But you can run your own in one command, and then your traffic never touches a third-party server.</p>
<p><strong>Tor proxy.</strong> If you need it: <code>--socks5 &quot;127.0.0.1:9050&quot;</code>.</p>
<p><strong>Piping works.</strong> <code>cat archive.tar.gz | croc send</code> and <code>croc --yes &lt;code&gt; &gt; output.tar.gz</code> both work cleanly. Good for scripts.</p>
<h2 id="hands-on">Hands On</h2>
<p><strong>Installation</strong></p>
<p>On Arch Linux:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">pacman</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -S</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> croc</span></span>
<span class="line"></span></code></pre>
<p>On Fedora/RHEL:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">dnf</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> croc</span></span>
<span class="line"></span></code></pre>
<p>Via the install script (works on most Linux distros):</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">curl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> https://getcroc.schollz.com</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> bash</span></span>
<span class="line"></span></code></pre>
<p>Or build from source if you have Go 1.22+:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">go</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> github.com/schollz/croc/v10@latest</span></span>
<span class="line"></span></code></pre>
<p>Always verify the install script against the <a href="https://github.com/schollz/croc">official repo</a> before piping to bash.</p>
<p><strong>Sending a file</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">croc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> send</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> backup.tar.gz</span></span>
<span class="line"></span></code></pre>
<p>Output:</p>
<pre data-lang-none><code>Sending 'backup.tar.gz' (142 MB)
Code is: fix-artist-moon
</code></pre>
<p>The code phrase copies to your clipboard automatically.</p>
<p><strong>Receiving it</strong></p>
<p>On another machine — any OS:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">croc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> fix-artist-moon</span></span>
<span class="line"></span></code></pre>
<p>That's the entire interface. On Linux, croc hides the secret from process listings by default. If you're transferring on a single-user machine and find the env-var approach annoying, run <code>croc --classic</code> once to make the simpler behavior permanent.</p>
<p><strong>Running your own relay</strong></p>
<p>On a VPS or your home server:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> run</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -d</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -p</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 9009-9013:9009-9013</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  -e</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> CROC_PASS='yourpassword'</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  docker.io/schollz/croc</span></span>
<span class="line"></span></code></pre>
<p>Then every transfer uses your relay:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">croc</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --relay</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "yourserver.example.com:9009"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --pass</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> yourpassword</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> send</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> file.tar.gz</span></span>
<span class="line"></span></code></pre>
<p>Recipients also need to specify the relay. You can make it the default by setting <code>CROC_RELAY</code> in your shell config — check the <a href="https://github.com/schollz/croc">official docs</a> for the current env var names, as these can change between versions.</p>
<h2 id="honest-verdict">Honest Verdict</h2>
<p>croc is not the fastest option. On a gigabit LAN, <code>scp</code> or <code>rsync</code> will outpace it — the encryption and relay overhead are measurable, and if both machines are on the same network you're routing through a relay unnecessarily. For bulk transfers between servers you have SSH access to, croc isn't the right tool.</p>
<p>But that's not the use case it's built for. croc earns its place when you need to move something between machines that can't reach each other directly — different networks, NAT, no shared SSH keys. That's where everything else falls apart and croc just works.</p>
<p>Self-hosting the relay removes the main trust concern (depending on a third-party server). At that point you have a solid, private file-drop system that's simpler to use than anything involving SSH keys, and it works for anyone you hand a code phrase to.</p>
<h2 id="go-try-it">Go Try It</h2>
<p>Install it. Send yourself a file from two different terminals on different networks. You'll understand why it exists in about 30 seconds.</p>
<p>→ <a href="https://github.com/schollz/croc">github.com/schollz/croc</a></p>
<!--kg-card-end: markdown-->]]></content:encoded>
  </item>
  <item>
    <title>Openship: Deploy to Your Own Server Without the YAML</title>
    <link>https://justmeandlinux.com/openship-deploy-to-your-own-server-without-the-yaml/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/openship-deploy-to-your-own-server-without-the-yaml/</guid>
    <pubDate>Tue, 21 Jul 2026 10:02:06 GMT</pubDate>
    <category>Self-Hosted</category>
    <description>Most self-hosted deployment tools replace one form of complexity with another. You trade cloud bills for YAML sprawl, Dockerfiles, pipeline configs, and a weekend of docs hunting. Openship is trying to break that cycle. What It Is Openship is an open-source…</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Most self-hosted deployment tools replace one form of complexity with another. You trade cloud bills for YAML sprawl, Dockerfiles, pipeline configs, and a weekend of docs hunting. Openship is trying to break that cycle.</p>
<h2 id="what-it-is">What It Is</h2>
<p>Openship is an open-source, self-hostable deployment platform. Think Heroku — but running on your own Linux server, under your control, with your data. It handles CI/CD, databases, SSL, domains, backups, and even a built-in mail server from one interface. You push code; it ships containers.</p>
<p>GitHub: <a href="https://github.com/oblien/openship">oblien/openship</a> · Website: <a href="https://openship.io">openship.io</a></p>
<h2 id="why-its-worth-your-time">Why It's Worth Your Time</h2>
<p>The &quot;zero config files, zero pipelines, zero YAML&quot; claim is what made me look twice.</p>
<p>Openship auto-detects your stack, configures builds, manages domains, provisions SSL via Let's Encrypt, and sets up DKIM/SPF/DMARC for email — without you touching a single config file. That's a real departure from how most self-hosted deployment tools work.</p>
<p>For anyone running a homelab or a VPS, the built-in mail server is the standout feature. No Mailgun account. No SendGrid API key. No third-party dependency for transactional email. It's all local, all yours.</p>
<p>The platform runs on anything: Hetzner, DigitalOcean, bare metal, your homelab server. Same interface regardless.</p>
<h2 id="hands-on">Hands On</h2>
<p>Install the CLI:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> i</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -g</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> openship</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># or if you prefer the install script:</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -fsSL</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> https://get.openship.io</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> sh</span></span>
<span class="line"></span></code></pre>
<p>Bring it up as a background service (starts on boot, auto-restarts):</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">openship</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> up</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">openship</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> open</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">   # opens the web dashboard</span></span>
<span class="line"></span></code></pre>
<p>Deploy a project:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">cd</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> your-project</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">openship</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> init</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    # links this directory to a project</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">openship</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> deploy</span></span>
<span class="line"></span></code></pre>
<p>If you're already comfortable with Docker Compose, there's a compose stack for the self-hosted install:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">git</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> clone</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> https://github.com/oblien/openship.git</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> &#x26;&#x26; </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">cd</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> openship</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">cp</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> .env.example</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> .env</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> compose</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> up</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -d</span></span>
<span class="line"></span></code></pre>
<p>What happens next is where it gets interesting. Openship detects your stack, builds it, configures routing, provisions SSL, and gets your app live. No pipeline YAML. No Nginx config. No Certbot dance.</p>
<p>It supports every mainstream stack: Node, Python, Go, Rust, PHP, Ruby, Java, .NET, and existing Docker Compose files as-is. Databases — Postgres, MySQL, MongoDB, Redis — are managed from the same dashboard.</p>
<h2 id="honest-verdict">Honest Verdict</h2>
<p>The docs are explicitly marked work-in-progress — the maintainers say so right in the README, which I respect. That honesty means you'll hit gaps. If your exact setup isn't covered, you're doing some inference.</p>
<p>Multi-node clusters and a load-balancing UI are listed as &quot;coming next,&quot; so if you're running a distributed setup today, you're not there yet.</p>
<p>What exists right now is compelling for the 90% case: solo devs and small teams shipping side projects or internal tools on a VPS or homelab, who don't want to become DevOps engineers to do it. The abstraction is right. The zero-YAML promise holds up for standard stacks.</p>
<p>It's been gaining serious traction — 5,300+ stars and climbing fast. Something about this one has resonated with people who've been disappointed by the alternatives.</p>
<h2 id="go-try-it">Go Try It</h2>
<p>Start here:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> i</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -g</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> openship</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> &#x26;&#x26; </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">openship</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> up</span></span>
<span class="line"></span></code></pre>
<p>Or clone the repo and use Docker Compose if that's your workflow. The <a href="https://openship.io/docs">official docs</a> are thin in places but enough to get moving.</p>
<p>GitHub: <a href="https://github.com/oblien/openship">https://github.com/oblien/openship</a></p>
<!--kg-card-end: markdown-->]]></content:encoded>
  </item>
  <item>
    <title>DocuSeal: Self-Host Your Document Signing</title>
    <link>https://justmeandlinux.com/docuseal-self-host-your-document-signing/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/docuseal-self-host-your-document-signing/</guid>
    <pubDate>Sat, 18 Jul 2026 10:02:07 GMT</pubDate>
    <category>Self-Hosted</category>
    <description>DocuSign charges $40/month. DocuSeal gives you the same core workflow in a Docker container that costs you nothing.</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>DocuSign charges $40/month for a feature set you'll use maybe twice a year. DocuSeal gives you the same core workflow in a Docker container that costs you nothing.</p>
<h2 id="what-it-is">What It Is</h2>
<p>DocuSeal is an open source document signing platform. Think DocuSign, but self-hosted, AGPL licensed, and running on a $5 VPS if you want it to. Upload a PDF, drop in signature fields, send a link, get it back signed. That's the loop. No subscription, no per-seat pricing, no data going to someone else's cloud.</p>
<p>It's been around since 2023 and has 11,800+ GitHub stars. This isn't an experiment — 191,000+ businesses and individuals are using it.</p>
<h2 id="why-its-worth-your-time">Why It's Worth Your Time</h2>
<p>The self-hosted document signing space has a graveyard of projects that were hard to run, harder to actually use, or just broke when you needed them. DocuSeal is different in one specific way: the signing UX is genuinely clean. You could send the link to a non-technical person and they'd figure it out without a tutorial.</p>
<p>The feature list is serious: WYSIWYG form builder, 12+ field types (signature, date, checkbox, text, file upload), automated emails via SMTP, S3/GCS/Azure storage support, a REST API, webhooks, conditional fields, bulk send via CSV, SSO/SAML. It's not a toy.</p>
<p>What separates it from the graveyard projects is that the default configuration is actually useful. SQLite out of the box, no separate database to stand up, no dozen ENV vars to figure out before you see a login screen.</p>
<h2 id="hands-on">Hands On</h2>
<p>Getting it running is one command:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> run</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --name</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> docuseal</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -p</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 3000:3000</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -v</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> .:/data</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> docuseal/docuseal</span></span>
<span class="line"></span></code></pre>
<p>Hit <code>localhost:3000</code>, create your admin account, done. The data directory holds your SQLite database and any uploaded files.</p>
<p>For a production setup with HTTPS and a real domain, pull the compose file:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">curl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> https://raw.githubusercontent.com/docusealco/docuseal/master/docker-compose.yml</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ></span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> docker-compose.yml</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sudo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> HOST=yourdomain.com</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> compose</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> up</span></span>
<span class="line"></span></code></pre>
<p>The compose file bundles Caddy as a reverse proxy. Point your DNS at the server, run that command, and TLS is handled automatically via Let's Encrypt. This is the part that usually takes an afternoon with other self-hosted tools. Here it's two commands.</p>
<p>The form builder is drag-and-drop. Upload your PDF, place fields exactly where you want them, assign submitters, hit send. Each submitter gets a unique signing link — they don't need an account. When they complete it, you get an email and the signed PDF is stored locally.</p>
<p>What works well: the mobile signing experience is actually good. The signature drawing is responsive, field navigation is clear. Signers don't complain. In document-signing land, that's high praise.</p>
<p>What's rough: the template system takes a minute to click. Templates are separate from submission requests, and the distinction isn't obvious at first. The naming could be clearer. The API documentation is solid, but the webhook payload structure isn't immediately obvious — budget some time for that if you're building an integration.</p>
<p>The <code>DATABASE_URL</code> environment variable switches you from SQLite to PostgreSQL or MySQL when you're ready to scale, and the storage backend supports S3-compatible providers if you'd rather not keep files on disk.</p>
<h2 id="honest-verdict">Honest Verdict</h2>
<p>If you ever need to collect signatures — contracts, NDAs, onboarding forms — DocuSeal earns its place in your self-hosted stack. The setup is fast, the interface doesn't fight you, and the API is good enough to automate the whole flow.</p>
<p>One thing to read before you deploy: the license is AGPLv3 with additional commercial terms (check the <code>LICENSE_ADDITIONAL_TERMS</code> file in the repo). For personal use, internal tooling, or direct document sending this doesn't matter. If you're planning to embed it in a product you sell, read it first.</p>
<p>The only genuine limitation: this is a web application, not a CLI. Everything goes through the browser UI or the API. There's no <code>docuseal sign contract.pdf</code> command. What it is: a clean, capable replacement for a SaaS tool that charges too much for what it does.</p>
<h2 id="go-try-it">Go Try It</h2>
<p>Run the single Docker command, upload a PDF, place a signature field, and sign it yourself. The whole loop takes under five minutes. That's the fastest way to know whether it fits your workflow.</p>
<p><strong>GitHub:</strong> <a href="https://github.com/docusealco/docuseal">github.com/docusealco/docuseal</a></p>
<!--kg-card-end: markdown-->]]></content:encoded>
  </item>
  <item>
    <title>dcg: The Shell Safety Net Your AI Coding Agent Needs</title>
    <link>https://justmeandlinux.com/dcg-the-shell-safety-net-your-ai-coding-agent-needs/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/dcg-the-shell-safety-net-your-ai-coding-agent-needs/</guid>
    <pubDate>Thu, 16 Jul 2026 10:01:41 GMT</pubDate>
    <category>Apps</category>
    <description>AI coding agents occasionally run catastrophic commands. dcg is a Rust-powered hook that intercepts destructive shell and git commands before they execute — sub-millisecond, context-aware, and worth five minutes of setup.</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>You've given an AI coding agent access to your shell. It's smart, fast, and genuinely useful — until it isn't. One stray <code>git reset --hard HEAD~5</code> or <code>rm -rf ./src</code> and three hours of uncommitted work are gone. The agent moves on. You don't.</p>
<p>This is the problem <code>dcg</code> (Destructive Command Guard) was built to solve.</p>
<h2 id="what-it-is">What It Is</h2>
<p><code>dcg</code> is a hook written in Rust that sits between your AI coding agent and the terminal. When an agent tries to execute a command, <code>dcg</code> evaluates it first. If it matches a known destructive pattern — <code>rm -rf</code>, <code>git reset --hard</code>, <code>DROP TABLE</code>, <code>kubectl delete namespace</code> — it blocks the command, explains why, and suggests a safer alternative.</p>
<p>It supports Claude Code, Codex CLI, Gemini CLI, GitHub Copilot CLI, Cursor, Grok, and a handful of others. Install it once and every supported agent on the machine gets the guardrail automatically.</p>
<h2 id="why-its-worth-your-time">Why It's Worth Your Time</h2>
<p>The obvious objection: &quot;just review what the agent runs.&quot; That works until it doesn't. AI agents move fast. In a long session you'll start rubber-stamping commands — especially when everything has been fine for the past 30 minutes.</p>
<p>What makes <code>dcg</code> genuinely useful is that it's not a dumb blocklist. It's context-aware. Running <code>grep &quot;rm -rf&quot; Makefile</code> is data — it gets through. Running <code>rm -rf /tmp/build</code> during a cleanup step the agent invented on its own — that's where you want a second opinion.</p>
<p>It ships with 50+ modular security packs: PostgreSQL, MySQL, Docker, Kubernetes, AWS, GCP, Azure, Terraform, Vault, and more. Each pack is opt-in, so you enable only what's relevant to your stack. It also scans heredocs and inline scripts — catching something like <code>python -c &quot;import shutil; shutil.rmtree('./src')&quot;</code> before it executes.</p>
<p>Latency is sub-millisecond thanks to SIMD-accelerated filtering. You won't notice it.</p>
<h2 id="hands-on">Hands On</h2>
<p>The install is a single curl command:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -fsSL</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "https://raw.githubusercontent.com/Dicklesworthstone/destructive_command_guard/main/install.sh?$(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">date</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> +%s)"</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> bash</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --easy-mode</span></span>
<span class="line"></span></code></pre>
<p>This auto-detects your platform, downloads the right binary, and configures hooks for whatever supported AI coding agents it finds on your system.</p>
<p>When an agent tries something dangerous, you see this instead of regret:</p>
<pre data-lang-none><code>════════════════════════════════════════════════════════
BLOCKED  dcg
────────────────────────────────────────────────────────
Reason:  git reset --hard destroys uncommitted changes

Command: git reset --hard HEAD~5

Tip: Consider using 'git stash' first to save your changes.
════════════════════════════════════════════════════════
</code></pre>
<p>If you <em>do</em> need to run a blocked command, <code>dcg allow-once &lt;code&gt;</code> lets a single execution through without permanently weakening your protection. The code appears in the denial panel — no hunting through config files.</p>
<p>To enable additional protection for databases and containers, edit <code>~/.config/dcg/config.toml</code>:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="toml"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">[</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">packs</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">enabled = [</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">    "database.postgresql"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">    "kubernetes.kubectl"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">    "containers.docker"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">    "cloud.aws"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"></span></code></pre>
<p>You can also tune trust per agent. If you want to give Claude Code a wider allowlist while locking down anything unknown:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="toml"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">[</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">agents</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">claude-code</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">trust_level = </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"high"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">additional_allowlist = [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"npm run build"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"cargo test"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">disabled_packs = [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"kubernetes"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">[</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">agents</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">unknown</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">trust_level = </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"low"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">disabled_allowlist = </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span></span>
<span class="line"></span></code></pre>
<p>And before you commit to a config change, you can test what would happen:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">dcg</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> explain</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "git reset --hard HEAD~5"</span></span>
<span class="line"></span></code></pre>
<p>This shows exactly which rule would fire and why — useful when you're tuning the allowlist and want to verify your changes before they matter.</p>
<h2 id="honest-verdict">Honest Verdict</h2>
<p>If you're using AI coding agents regularly — especially on projects with uncommitted changes or live infrastructure — <code>dcg</code> is worth five minutes of setup. It's not perfect. The fail-open design means parse errors and timeouts let commands through rather than blocking them, which is the right call for developer ergonomics but means this isn't a hard security boundary. It also carries a custom license rather than a standard OSI-approved one, worth checking before adoption in certain organizational contexts.</p>
<p>The core idea is exactly right. Context-aware filtering is what separates it from a glorified blocklist. It catches the catastrophic stuff — the commands where the window between &quot;running&quot; and &quot;irreversible&quot; is effectively zero.</p>
<p>I wouldn't call it a replacement for reviewing what your agent does. Think of it as a seatbelt. You still drive carefully. But you wear it anyway.</p>
<h2 id="go-try-it">Go Try It</h2>
<p>→ <a href="https://github.com/Dicklesworthstone/destructive_command_guard">github.com/Dicklesworthstone/destructive_command_guard</a></p>
<p>Start with <code>--easy-mode</code>. Then run <code>dcg explain</code> on a few commands from your typical workflow to understand exactly what's being watched.</p>
<!--kg-card-end: markdown-->]]></content:encoded>
  </item>
  <item>
    <title>OfficeCLI: Read and Edit Office Documents Without Microsoft Office</title>
    <link>https://justmeandlinux.com/officecli-read-and-edit-office-documents-without-microsoft-office/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/officecli-read-and-edit-office-documents-without-microsoft-office/</guid>
    <pubDate>Sat, 11 Jul 2026 10:01:41 GMT</pubDate>
    <category>Apps</category>
    <description>A single binary that reads, writes, and creates Word, Excel, and PowerPoint files on Linux — no Office, no LibreOffice, no conversion artifacts.</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>If you've ever been emailed a <code>.docx</code> or <code>.pptx</code> on a Linux box and thought <em>&quot;here we go&quot;</em> — OfficeCLI might end that friction.</p>
<h2 id="what-it-is">What It Is</h2>
<p>OfficeCLI is a self-contained binary that reads, writes, and creates Word, Excel, and PowerPoint files — with no Office installation, no LibreOffice, no conversion artifacts. You treat documents like files you can query and modify from the terminal. The binary includes an embedded .NET runtime (NativeAOT compiled), so there's nothing to install beyond the tool itself.</p>
<p>It navigates document structure using XPath-style paths. <code>/slide[1]/shape[2]</code> selects the second shape on the first slide. You query, you modify, you close. That's the entire mental model.</p>
<h2 id="why-its-worth-your-time">Why It's Worth Your Time</h2>
<p>Every Linux user has a workflow for Office documents. Usually it involves LibreOffice rendering things slightly wrong, or 50 lines of <code>python-pptx</code> boilerplate, or just emailing the file to yourself and opening it on another machine.</p>
<p>OfficeCLI takes a different angle. It treats the document as a structured tree — not a black box — and gives you clean, composable commands to interact with it. The live-preview mode is the standout feature: run <code>officecli watch deck.pptx</code> and you get a local HTTP server on port 26315 with an auto-refreshing preview. Edit a slide from the CLI and the browser updates immediately. That feedback loop matters.</p>
<p>The Excel formula evaluation is also genuinely clever. You write <code>=SUM(A1:A2)</code>, read the cell back, and the value is already computed — no round-trip through a spreadsheet to recalculate. Covers 350+ functions including dynamic arrays (<code>FILTER</code>, <code>SORT</code>, <code>UNIQUE</code>), <code>XLOOKUP</code>, financial math, and more.</p>
<h2 id="hands-on">Hands On</h2>
<p>Installation on Linux is a one-liner (verify this from their GitHub before running):</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -fsSL</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/main/install.sh</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> bash</span></span>
<span class="line"></span></code></pre>
<p>Alternatively via npm — which fetches the native binary for your platform:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -g</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> @officecli/officecli</span></span>
<span class="line"></span></code></pre>
<p>Or download the binary directly from <a href="https://github.com/iOfficeAI/OfficeCLI/releases">GitHub Releases</a> (both <code>linux-x64</code> and <code>linux-arm64</code> builds available).</p>
<p>Verify it's working:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">officecli</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --version</span></span>
<span class="line"></span></code></pre>
<p>Create a presentation and add content:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">officecli</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> create</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> deck.pptx</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">officecli</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> add</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> deck.pptx</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --type</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> slide</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --prop</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> title="Q4 Report"</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">officecli</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> view</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> deck.pptx</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> outline</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># → Slide 1: Q4 Report</span></span>
<span class="line"></span></code></pre>
<p>Start live preview in your browser:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">officecli</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> watch</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> deck.pptx</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># http://localhost:26315 — auto-refreshes on every change</span></span>
<span class="line"></span></code></pre>
<p>Read a document element as structured JSON:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">officecli</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> get</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> report.docx</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /body/p[1]</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --json</span></span>
<span class="line"></span></code></pre>
<p>For batch operations — say, updating text across 100 files — the resident mode keeps the document in memory for near-zero latency between commands:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">officecli</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> open</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> report.docx</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">officecli</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> set</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> report.docx</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /body/p[1]/r[1]</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --prop</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> bold=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">officecli</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> set</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> report.docx</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /body/p[2]/r[1]</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --prop</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> color=FF0000</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">officecli</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> close</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> report.docx</span></span>
<span class="line"></span></code></pre>
<p>There's also a <code>merge</code> command that replaces <code>{{key}}</code> placeholders in any <code>.docx</code>/<code>.xlsx</code>/<code>.pptx</code> with JSON data. Design the template once, fill it N times, deterministically:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">officecli</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> merge</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> invoice-template.docx</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> out-001.docx</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '{"client":"Acme","total":"$5,200"}'</span></span>
<span class="line"></span></code></pre>
<p>That last one is the kind of thing you'd previously reach for a full templating library to do.</p>
<h2 id="honest-verdict">Honest Verdict</h2>
<p>This is the tool I wish existed when I was dealing with Office files on a headless server. LibreOffice has always been the fallback, and it's never quite right. OfficeCLI gives you programmatic, structured access to Office formats without conversion surprises.</p>
<p>A few caveats worth naming. The install method is <code>curl | bash</code> — run it in a throwaway environment first if that bothers you, or use the direct binary download. Auto-updates are enabled by default; disable them if you want control over what's on your system:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">officecli</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> config</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> autoUpdate</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> false</span></span>
<span class="line"></span></code></pre>
<p>It's also a product from a company (iOfficeAI), not a community project. The heavy marketing angle is toward AI agents, and the README leans into that hard. Look past it — the tool works fine as a standalone CLI with no AI anywhere in the picture.</p>
<p>The docs are thorough but live in a GitHub wiki, which takes some navigation to get used to.</p>
<p><strong>Who it's actually for:</strong> server environments where LibreOffice isn't an option, CI pipelines that generate reports, anyone tired of writing python-pptx boilerplate.</p>
<h2 id="go-try-it">Go Try It</h2>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Install</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -fsSL</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/main/install.sh</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> bash</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Create a file and see it live</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">officecli</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> create</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> test.pptx</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">officecli</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> add</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> test.pptx</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --type</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> slide</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --prop</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> title="Hello from the terminal"</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">officecli</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> watch</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> test.pptx</span></span>
<span class="line"></span></code></pre>
<p><strong>GitHub:</strong> <a href="https://github.com/iOfficeAI/OfficeCLI">iOfficeAI/OfficeCLI</a></p>
<!--kg-card-end: markdown-->]]></content:encoded>
  </item>
  <item>
    <title>PhotoGIMP: Make GIMP Feel Like Home After Photoshop</title>
    <link>https://justmeandlinux.com/photogimp-make-gimp-feel-like-home-after-photoshop/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/photogimp-make-gimp-feel-like-home-after-photoshop/</guid>
    <pubDate>Thu, 09 Jul 2026 10:02:07 GMT</pubDate>
    <category>Apps</category>
    <description>You&apos;ve spent years building Photoshop muscle memory. Every time you sit down in GIMP, your hands try to use it — and GIMP doesn&apos;t cooperate. PhotoGIMP fixes that. It&apos;s a configuration patch for GIMP 3.0+ that remaps keyboard shortcuts to match Adobe…</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>You've spent years building Photoshop muscle memory. Every time you sit down in GIMP, your hands try to use it — and GIMP doesn't cooperate.</p>
<p><strong>PhotoGIMP</strong> fixes that. It's a configuration patch for GIMP 3.0+ that remaps keyboard shortcuts to match Adobe Photoshop's layout, repositions the tool panels into a familiar arrangement, and sets sane UI defaults to maximize your canvas. It's not a fork. Not a plugin. Not another Electron app trying to replace something. It's a zip file that drops config files into <code>~/.config/GIMP/3.0/</code> — and then stays out of your way.</p>
<h2 id="why-this-exists">Why This Exists</h2>
<p>GIMP is the best free image editor on Linux, and the gap between it and Photoshop has closed significantly. GIMP 3.0 brought proper color management, better UI scaling, and a layer system that's finally approaching what Photoshop users expect.</p>
<p>The reason most designers don't switch isn't capability. It's friction.</p>
<p><code>Ctrl+T</code> is Free Transform in Photoshop. In vanilla GIMP, it does something else. <code>B</code> for Brush. <code>V</code> for the Move tool. <code>Ctrl+J</code> to duplicate a layer. Multiply those moments across an entire workflow and you've got a technically capable tool that feels like typing with gloves on. Muscle memory is powerful — and muscle memory built over years in Photoshop doesn't care how good GIMP is in theory.</p>
<p>PhotoGIMP removes the gloves.</p>
<h2 id="installing-it">Installing It</h2>
<p>First, back up your existing GIMP config. PhotoGIMP overwrites it.</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">cp</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -r</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ~/.config/GIMP/3.0</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ~/GIMP-3.0-backup</span></span>
<span class="line"></span></code></pre>
<p>Then grab the latest release from <a href="https://github.com/Diolinux/PhotoGIMP/releases/latest">GitHub</a> and extract it into your home folder:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Assuming you downloaded to ~/Downloads:</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">cd</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ~</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">unzip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ~/Downloads/PhotoGIMP-linux.zip</span></span>
<span class="line"></span></code></pre>
<p>That places files into <code>~/.config</code> and <code>~/.local</code> — standard XDG locations, no sudo needed. Open GIMP and you'll see the new layout.</p>
<p><strong>One thing to know before you start:</strong> run GIMP once before installing PhotoGIMP. GIMP generates its config directories on first launch — if they don't exist yet, the patch has nothing to write into.</p>
<p>If you installed GIMP from Flatpak or from your distro's package manager (apt, dnf, pacman), the config location is the same: <code>~/.config/GIMP/3.0/</code>. The install steps above work either way.</p>
<h2 id="what-it-actually-changes">What It Actually Changes</h2>
<p>PhotoGIMP replaces a handful of specific config files:</p>
<ul>
<li><strong><code>shortcutsrc</code></strong> — GIMP's keyboard shortcut map, remapped to follow Adobe's official Photoshop (Windows) layout</li>
<li><strong><code>toolrc</code></strong> — tool ordering and configuration, repositioned to mirror Photoshop's toolbox</li>
<li><strong><code>sessionrc</code> / <code>dockrc</code></strong> — window layout and panel positions</li>
<li><strong><code>gimprc</code></strong> — general preferences (canvas defaults, grid, etc.)</li>
<li><strong><code>splashes/</code></strong> — custom PhotoGIMP splash screen on startup</li>
<li>A custom <code>.desktop</code> file and icon for your app launcher (Linux only)</li>
</ul>
<p>It does <strong>not</strong> touch your personal brushes, gradients, fonts, or plugins. Those live in separate GIMP data directories and are left completely alone.</p>
<h2 id="the-honest-part">The Honest Part</h2>
<p>This is a smart patch, not a magic transformation.</p>
<p>The shortcuts follow Adobe's <em>Windows</em> documentation. If you were a Mac Photoshop user with remapped modifiers, you'll still hit some friction. And some shortcuts you expect simply won't be there — not because PhotoGIMP missed them, but because the underlying feature doesn't exist in GIMP yet. No amount of config remapping gives you Content-Aware Fill.</p>
<p>There are also things PhotoGIMP can't fix at the config level. Some Photoshop workflows depend on features GIMP handles differently by design — adjustment layers, smart objects, non-destructive filters. Those gaps are smaller than they used to be in GIMP 3.0, but they're real. Knowing the tool's actual limits beats finding them mid-project.</p>
<p>One maintenance note: if GIMP releases a new major version that changes its config format, you'll likely need to reinstall PhotoGIMP. It already went through this once — the current version is strictly GIMP 3.0+, with no backward compatibility to GIMP 2.x.</p>
<h2 id="verdict">Verdict</h2>
<p>If you moved to Linux and GIMP has felt like relearning everything from scratch, PhotoGIMP is worth 60 seconds of your time. It won't make GIMP into Photoshop — nothing will — but it removes the biggest daily friction point: your hands know what to do, and now the tool responds correctly.</p>
<p>It's not for people who never used Photoshop. Vanilla GIMP's defaults make more sense in isolation, and switching to Photoshop-style shortcuts would just be trading one set of unfamiliar bindings for another. Install it only if your hands already have the muscle memory.</p>
<p>Uninstalling is clean:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">rm</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -rf</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ~/.config/GIMP/3.0</span></span>
<span class="line"></span></code></pre>
<p>GIMP regenerates clean defaults on next launch. No residue.</p>
<h2 id="go-try-it">Go Try It</h2>
<p>Install GIMP 3.0+ if you haven't. Open it once to generate config directories. Download the Linux zip from <a href="https://github.com/Diolinux/PhotoGIMP">Diolinux/PhotoGIMP on GitHub</a> — check the releases tab. Extract to <code>~</code>. Open GIMP.</p>
<p>Takes under a minute. The first time <code>Ctrl+T</code> does exactly what you expect, you'll know whether to keep it.</p>
<!--kg-card-end: markdown-->]]></content:encoded>
  </item>
  <item>
    <title>Karakeep: a self-hosted replacement for Pocket</title>
    <link>https://justmeandlinux.com/karakeep-a-self-hosted-bookmark-vault-that-actually-thinks/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/karakeep-a-self-hosted-bookmark-vault-that-actually-thinks/</guid>
    <pubDate>Tue, 07 Jul 2026 10:02:11 GMT</pubDate>
    <category>Self-Hosted</category>
    <description>Mozilla killed Pocket. Your browser bookmarks are a graveyard. Karakeep is a self-hosted bookmark manager with AI tagging, full-page archiving, and video hoarding — and it runs entirely on your own hardware.</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Mozilla killed Pocket. Your browser's built-in bookmark folder is a disorganized mess you haven't opened in months. And every read-it-later SaaS you've trusted is one acquisition away from shutting down.</p>
<p>Karakeep solves this — and it runs on your hardware.</p>
<h2 id="what-it-is">What It Is</h2>
<p>Karakeep (formerly called Hoarder) is a self-hosted bookmark manager that goes well beyond links. You throw URLs, images, PDFs, and notes at it. It pulls title and description previews automatically, tags everything with an LLM, and makes it all full-text searchable. Think Pocket, but you own the data — and it actually understands what you saved.</p>
<p>The name comes from &quot;كراكيب&quot; (karakeeb) — Arabic slang for that drawer full of odds and ends you can't quite bring yourself to throw away. If that describes your browser bookmarks tab, this tool was built for you.</p>
<h2 id="why-its-worth-your-time">Why It's Worth Your Time</h2>
<p>Three things set it apart:</p>
<p><strong>AI tagging that respects your privacy.</strong> Karakeep uses an LLM to automatically tag everything you save. Point it at OpenAI, or wire it to a local <a href="https://ollama.com">Ollama</a> instance and keep it entirely offline. Automatic organization without handing your reading list to anyone.</p>
<p><strong>Full-page archiving against link rot.</strong> It uses <a href="https://github.com/Y2Z/monolith">monolith</a> to save a complete snapshot of every page you bookmark. Links disappear. Paywalls appear. Domains expire. With karakeep, you have the content, not just a dead reference.</p>
<p><strong>Video archiving via yt-dlp.</strong> Bookmark a YouTube URL and it archives the video locally. Combined with Meilisearch-powered full-text search, you end up with a searchable personal knowledge vault — links, articles, videos, images, and notes all in one place.</p>
<p>There are browser extensions for Chrome, Firefox, and Safari. Native iOS and Android apps exist. RSS feed ingestion is built in — set it to auto-pull and archive items from feeds you follow.</p>
<h2 id="hands-on">Hands On</h2>
<p>The install is clean Docker Compose. Start here:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">mkdir</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> karakeep-app</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> &#x26;&#x26; </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">cd</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> karakeep-app</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">wget</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> https://raw.githubusercontent.com/karakeep-app/karakeep/main/docker/docker-compose.yml</span></span>
<span class="line"></span></code></pre>
<p>Create your <code>.env</code> file:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="ini"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">KARAKEEP_VERSION</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=release</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">NEXTAUTH_SECRET</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=&#x3C;run: openssl rand -base64 36></span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">MEILI_MASTER_KEY</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=&#x3C;run: openssl rand -base64 36></span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">NEXTAUTH_URL</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=http://your-server-ip:3000</span></span>
<span class="line"></span></code></pre>
<p>Then spin it up:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> compose</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> up</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -d</span></span>
<span class="line"></span></code></pre>
<p>Visit port 3000, create your account, and start bookmarking. For local AI tagging with Ollama, the <a href="https://docs.karakeep.app/configuration/different-ai-providers">AI providers guide</a> walks through pointing karakeep at your local model endpoint.</p>
<p>One thing worth knowing upfront: this is a multi-container stack. Karakeep runs alongside Meilisearch and a browser crawler service. On a capable home server that's fine. On a low-memory VPS, plan your resources accordingly.</p>
<h2 id="honest-verdict">Honest Verdict</h2>
<p>This is the tool I've been looking for since Pocket died. The core works: links in, previews generated, tags applied, full-text search works. The archiving is genuinely useful — not something most alternatives offer out of the box, and not something you realize you need until the third time a link you saved goes dead.</p>
<p>The rough edges: it's still under active development (their words, not mine), the config surface area grows fast when you enable advanced features, and Meilisearch adds meaningful memory overhead. SSO setup is also non-trivial if you're going down that road.</p>
<p>Who it's for: anyone running a homelab who actually reads the things they save. If your Pocket, Raindrop, or browser bookmarks folder holds more than a hundred dusty links, this pays off quickly.</p>
<h2 id="go-try-it">Go Try It</h2>
<p>Live demo at <a href="https://try.karakeep.app">try.karakeep.app</a> — login with <code>demo@karakeep.app</code> / <code>demodemo</code>.</p>
<p>→ GitHub: <a href="https://github.com/karakeep-app/karakeep">github.com/karakeep-app/karakeep</a><br>
→ Docs: <a href="https://docs.karakeep.app">docs.karakeep.app</a></p>
<!--kg-card-end: markdown-->]]></content:encoded>
  </item>
  <item>
    <title>herdr: tmux, Rebuilt for the Age of Coding Agents</title>
    <link>https://justmeandlinux.com/herdr-tmux-rebuilt-for-the-age-of-coding-agents/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/herdr-tmux-rebuilt-for-the-age-of-coding-agents/</guid>
    <pubDate>Thu, 02 Jul 2026 10:02:16 GMT</pubDate>
    <category>Apps</category>
    <description>You&apos;re running three coding agents at once. One is building out the API layer. Another is writing tests. A third is fixing that CSS mess you&apos;ve been ignoring for weeks. Which one needs you right now? With tmux, you&apos;d be squinting at six panes trying to…</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>You're running three coding agents at once. One is building out the API layer. Another is writing tests. A third is fixing that CSS mess you've been ignoring for weeks. Which one needs you right now?</p>
<p>With tmux, you'd be squinting at six panes trying to remember which terminal belongs to which task — tabbing through windows hoping to catch a &quot;waiting for input&quot; prompt before the agent times out. It's manageable for one agent. For three, it's a mess.</p>
<p>herdr solves exactly this.</p>
<h2 id="what-it-is">What It Is</h2>
<p>herdr is a terminal multiplexer — think tmux — rebuilt from scratch with AI coding agents as the primary user. It's a single Rust binary around 10MB in size. No GUI, no Electron, no account required. Each agent gets a real terminal, not a wrapper's simulation of one, so even full-screen TUIs render correctly. And the sidebar tells you, at a glance, which agents are 🔴 blocked, 🟡 working, 🔵 done, or 🟢 idle.</p>
<p>That last part is the whole product.</p>
<h2 id="why-its-worth-your-time">Why It's Worth Your Time</h2>
<p>Here's the thing about tmux: it was built before coding agents existed. It has no concept of what's running in a pane — it just renders characters. To get any agent state visibility with tmux, you'd wire up bells, custom hooks, and per-harness integrations. And even then, you'd have a patchwork that breaks whenever an agent changes its output format.</p>
<p>herdr does this with zero config. It uses process-name matching plus terminal-output heuristics to detect agent state automatically. Claude Code, Codex, Cursor, Hermes, Amp, OpenCode, Devin, GitHub Copilot CLI, and a dozen others are supported out of the box. If your agent isn't on the list, herdr still runs it — you just get multiplexing without the state detection.</p>
<p>The other thing that matters: nothing dies when you disconnect. A background server keeps every pane and agent alive. Detach from your laptop, walk away, reattach from another machine — or your phone over SSH — same session, same agents, still running. This is tmux's original killer feature, and herdr carries it forward intact.</p>
<p><code>herdr --remote ssh://you@yourserver</code> turns your local terminal into a client of a remote herdr server. Clipboard paste into agents works over this connection — the thing plain SSH + tmux breaks because there's no way to pass the pasteboard across the wire.</p>
<h2 id="hands-on">Hands On</h2>
<p><strong>Install:</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -fsSL</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> https://herdr.dev/install.sh</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> sh</span></span>
<span class="line"></span></code></pre>
<p>Also available via Homebrew (<code>brew install herdr</code>), mise (<code>mise use -g herdr</code>), or Nix (<code>nix run github:ogulcancelik/herdr</code>). Linux and macOS binaries are on the <a href="https://github.com/ogulcancelik/herdr/releases">releases page</a>.</p>
<p><strong>Start it:</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">herdr</span></span>
<span class="line"></span></code></pre>
<p>That's it. herdr checks for a running background server and starts one if needed, then opens a workspace. Drop into a pane and run your agent.</p>
<p><strong>Keybindings</strong> follow the same prefix model as tmux — <code>ctrl+b</code> then the action key, so muscle memory transfers:</p>
<ul>
<li><code>ctrl+b</code> then <code>v</code> or <code>-</code> — split the current pane</li>
<li><code>ctrl+b</code> then <code>c</code> — new tab</li>
<li><code>ctrl+b</code> then <code>Shift+N</code> — new workspace</li>
<li><code>ctrl+b</code> then <code>w</code> — switch workspaces</li>
<li><code>ctrl+b</code> then <code>q</code> — detach (agents keep running in the background)</li>
<li><code>ctrl+b</code> then <code>?</code> — show all bindings</li>
</ul>
<p>Mouse works throughout. Click to focus panes, drag borders to resize. For a terminal multiplexer, this is genuinely pleasant.</p>
<p><strong>Connecting to a remote server:</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">herdr</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --remote</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> workbox</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">herdr</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --remote</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ssh://you@yourserver:2222</span></span>
<span class="line"></span></code></pre>
<p>If you're running agents on a VPS or a more powerful machine and want to supervise them from your laptop — or your phone — this is the flag you want. The local terminal becomes a client of the remote herdr server. It's not screen sharing; it's the actual terminal session.</p>
<h2 id="honest-verdict">Honest Verdict</h2>
<p>It's v0.4.0 and it shows in places. Windows support is beta. The AGPL-3.0 license is a real consideration for organizations — commercial licenses exist, but it's a conversation you'd need to have with your legal team before using it in a company setting. And there's no revenue behind the project yet; it's one person building in the open and asking for sponsors. That's the longevity question every early adopter has to sit with.</p>
<p>But the core works cleanly. The agent-state sidebar is the feature that tmux will never ship because it would require tmux to know what you're running, and that's not in its design charter. herdr was designed from day one assuming the primary use case is humans supervising AI agents — and that constraint produces a sharper tool.</p>
<p>If you're running more than one coding agent regularly, the evaluation cost here is nearly zero: one small binary, one command, no accounts, no configuration. Try it for a week and see if you go back.</p>
<h2 id="go-try-it">Go Try It</h2>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -fsSL</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> https://herdr.dev/install.sh</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> sh</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">herdr</span></span>
<span class="line"></span></code></pre>
<p>Open a few panes, start your agents, and watch the sidebar. You'll immediately understand what it's for.</p>
<p>→ <a href="https://github.com/ogulcancelik/herdr">herdr on GitHub</a> · <a href="https://herdr.dev">herdr.dev</a></p>
<!--kg-card-end: markdown-->]]></content:encoded>
  </item>
  <item>
    <title>maigret: Find Every Account Tied to a Username Across 3,000+ Sites</title>
    <link>https://justmeandlinux.com/maigret-find-every-account-tied-to-a-username-across-3-000-sites/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/maigret-find-every-account-tied-to-a-username-across-3-000-sites/</guid>
    <pubDate>Tue, 30 Jun 2026 10:02:00 GMT</pubDate>
    <category>Apps</category>
    <description>You registered an account on a forum in 2011, used the same handle on six other platforms, and forgot about all of them. They&apos;re still out there. maigret finds them.</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>You registered an account on a forum in 2011, used the same handle on six other platforms, and forgot about all of them. They're still out there. maigret finds them.</p>
<h2 id="what-it-is">What It Is</h2>
<p>maigret is a Python CLI tool that searches for a username across 3,000+ websites and assembles whatever public information it finds into a report. No API keys required. No accounts to create. Just a username and a few minutes.</p>
<p>By default it checks the top 500 highest-traffic sites. Pass <code>-a</code> and it scans everything in the database. The output includes confirmed accounts with profile links, any scraped metadata (bios, names, locations), and connections to <em>other</em> usernames it discovers along the way — which feeds back into another round of searching.</p>
<h2 id="why-its-worth-your-time">Why It's Worth Your Time</h2>
<p>Most people reuse one or two usernames across their entire online life. That's convenience turned into a privacy liability — and it's invisible unless you go looking for it.</p>
<p>maigret makes that footprint visible. Not for stalking strangers, but for auditing yourself. Every forgotten account is a password you haven't changed in years, sitting on a platform that may have been breached since you last logged in.</p>
<p>The recursive search is what separates it from simpler alternatives like Sherlock. When maigret finds an account, it extracts linked usernames or IDs from that profile and searches for those too. One username becomes a graph.</p>
<h2 id="hands-on">Hands On</h2>
<p><strong>Install:</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">pip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> maigret</span></span>
<span class="line"></span></code></pre>
<p><strong>Run a default scan</strong> (top 500 sites by traffic):</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">maigret</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> yourusername</span></span>
<span class="line"></span></code></pre>
<p>You'll see results roll in as it scans — confirmed hits with links, likely false positives flagged differently.</p>
<p><strong>Full scan across all 3,000+ sites:</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">maigret</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> yourusername</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -a</span></span>
<span class="line"></span></code></pre>
<p><strong>Generate an HTML report</strong> with a browsable, interactive results graph:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">maigret</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> yourusername</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --html</span></span>
<span class="line"></span></code></pre>
<p>Open that file in a browser. It renders a D3 visualization of every found account and how they connect. Useful when a username is tied to multiple personas or linked platforms.</p>
<p><strong>Filter by category</strong> — only check photo and dating platforms:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">maigret</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> yourusername</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --tags</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> photo,dating</span></span>
<span class="line"></span></code></pre>
<p><strong>Launch the built-in web UI:</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">maigret</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --web</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 5000</span></span>
<span class="line"></span></code></pre>
<p>That spins up a local server at <code>http://127.0.0.1:5000</code> where you can run searches and pull any report format from a single page. If you want this on a home server, Docker makes it cleaner:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> run</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -p</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 5000:5000</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> soxoj/maigret:web</span></span>
<span class="line"></span></code></pre>
<p><strong>What's rough:</strong></p>
<p>False positives are real. Common usernames hit dozens of sites where a completely different person registered. You'll need to click through and verify — don't assume every result is yours.</p>
<p>A growing slice of sites hide behind Cloudflare JS challenges. Bypassing those requires a separate <a href="https://github.com/FlareSolverr/FlareSolverr">FlareSolverr</a> container running alongside maigret. It works, but it's extra setup most people won't bother with.</p>
<p>PDF export on Linux needs additional system graphics libraries. The HTML report is the easier path and honestly more useful anyway.</p>
<p>Speed on a full <code>-a</code> scan depends on your connection and how aggressively sites block datacenter IPs. Running from a VPS? Expect more blocks than from a residential connection.</p>
<h2 id="honest-verdict">Honest Verdict</h2>
<p>maigret does one thing well: it shows you exactly where a username has been, and what public information is attached to it. For self-auditing, it's the most capable free tool in this space.</p>
<p>It's not a magic investigation machine. The results require human judgment. The database drifts as platforms change their detection behavior. And you should read their note on lawful use seriously — this is a tool with obvious dual-use potential.</p>
<p>But for a <code>pip install</code> and a five-minute scan? Run it on your own handle. The result will either be boring (good) or alarming (also useful). Either way you'll know something you didn't before.</p>
<p>If you run a home lab and use the same handle across GitHub, forums, Discord servers, and random dev platforms from a decade ago — start there.</p>
<h2 id="go-try-it">Go Try It</h2>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">pip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> maigret</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">maigret</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> yourusername</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --html</span></span>
<span class="line"></span></code></pre>
<p>Open the HTML file. See where you live on the internet.</p>
<ul>
<li><strong>GitHub:</strong> <a href="https://github.com/soxoj/maigret">github.com/soxoj/maigret</a></li>
<li><strong>Docs:</strong> <a href="https://maigret.readthedocs.io">maigret.readthedocs.io</a></li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded>
  </item>
  <item>
    <title>TREK: self-hosted trip planning after Google Trips</title>
    <link>https://justmeandlinux.com/trek-a-self-hosted-trip-planner-that-actually-works/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/trek-a-self-hosted-trip-planner-that-actually-works/</guid>
    <pubDate>Sat, 27 Jun 2026 10:02:13 GMT</pubDate>
    <category>Self-Hosted</category>
    <description>Google killed Google Trips. TripIt wants your credit card. And shared Google Sheets &amp;quot;trip planners&amp;quot; are just organized suffering. If you&apos;ve been self-hosting everything else — photos, documents, notes — it never made sense to hand your travel data…</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Google killed Google Trips. TripIt wants your credit card. And shared Google Sheets &quot;trip planners&quot; are just organized suffering. If you've been self-hosting everything else — photos, documents, notes — it never made sense to hand your travel data to someone else. TREK fixes that.</p>
<h2 id="what-it-is">What It Is</h2>
<p>TREK is a self-hosted trip planner that runs as a single Docker container. It handles day-by-day itineraries with drag-and-drop, interactive maps, flight and hotel reservations, expense tracking with split calculations, packing lists, weather forecasts, and real-time sync so travel partners see changes the moment you make them. The backend is SQLite — no separate database to manage.</p>
<p>Previously shipped as NOMAD, it was renamed to TREK earlier this year. The project has been moving fast.</p>
<h2 id="why-its-worth-your-time">Why It's Worth Your Time</h2>
<p>Most &quot;self-hosted alternatives&quot; in this space are hobbyist projects that handle the basics and call it done. TREK went further.</p>
<p>The real-time collaboration works over WebSockets. Not polling, not a refresh button — someone adds a restaurant to Day 2 in Tokyo and it appears on your screen immediately.</p>
<p>Maps support both Leaflet with OpenStreetMap (no API key, no cost) and Mapbox GL with 3D buildings and terrain. You can import Google Maps saved lists, KML files, and GPX routes directly. Weather forecasts pull from Open-Meteo — free, no key required.</p>
<p>What actually surprised me: the expense tracker does multi-currency conversion and Splitwise-style settle-up calculations. The &quot;Journey&quot; feature is a proper travel journal with entries, photos, maps, and moods. The &quot;Atlas&quot; view tracks countries you've visited on a world map with stats and streaks.</p>
<p>It installs as a PWA on iOS and Android straight from the browser. No App Store, no TestFlight, no APK sideloading. Open the URL, tap &quot;Add to Home Screen,&quot; and it launches fullscreen like a native app.</p>
<p>SSO support is real: Google, Apple, Authentik, Keycloak, or any OIDC provider. 2FA with TOTP and passkeys (WebAuthn). If you've got an existing auth stack, TREK slots in without friction.</p>
<h2 id="hands-on">Hands On</h2>
<p>The fastest way to kick the tires:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">ENCRYPTION_KEY</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">$(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">openssl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> rand</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -hex</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 32</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> run</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -d</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -p</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 3000:3000</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  -e</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ENCRYPTION_KEY=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">$ENCRYPTION_KEY </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  -v</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ./data:/app/data</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -v</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ./uploads:/app/uploads</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  --name</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> trek</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --restart</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> unless-stopped</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  mauriceboe/trek</span></span>
<span class="line"></span></code></pre>
<p>Open <code>http://localhost:3000</code>. On first boot, TREK seeds an admin account — set <code>ADMIN_EMAIL</code> and <code>ADMIN_PASSWORD</code> as env vars to choose your credentials, or they'll be printed to the container log (<code>docker logs trek</code>).</p>
<p>For production, the repo includes a Docker Compose setup with hardened security defaults — read-only filesystem, dropped capabilities, tmpfs for <code>/tmp</code>. Here's the important part of it:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="yaml"><code><span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">services</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  app</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    image</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">mauriceboe/trek:latest</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    container_name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">trek</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    read_only</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    security_opt</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">no-new-privileges:true</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    cap_drop</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">ALL</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    cap_add</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">CHOWN</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">SETUID</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">SETGID</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    ports</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"3000:3000"</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    environment</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">NODE_ENV=production</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">ENCRYPTION_KEY=${ENCRYPTION_KEY}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">APP_URL=https://trek.yourdomain.com</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    volumes</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">./data:/app/data</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">./uploads:/app/uploads</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    restart</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">unless-stopped</span></span>
<span class="line"></span></code></pre>
<p>One thing worth knowing about volumes: <strong>only mount <code>./data</code> and <code>./uploads</code></strong>. Never mount a volume at <code>/app</code> — it hides the application code shipped in the image and the container won't start. The README is explicit about this, but it's the kind of thing you'd only know to look for after it bites you.</p>
<p>For OIDC/SSO, add these environment variables:</p>
<pre data-lang-none><code>OIDC_ISSUER=https://auth.example.com
OIDC_CLIENT_ID=trek
OIDC_CLIENT_SECRET=supersecret
APP_URL=https://trek.yourdomain.com
</code></pre>
<p><code>APP_URL</code> is required for OIDC to work — it's used as the base for callback URLs. Skip it and the whole auth flow breaks silently.</p>
<p>For the reverse proxy, TREK uses WebSockets for real-time sync. The proxy must handle WebSocket upgrades on <code>/ws</code>. With Nginx that means a separate location block:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="nginx"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">location</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> /ws </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    proxy_pass </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">http://localhost:3000;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    proxy_http_version </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1.1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    proxy_set_header </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Upgrade $http_upgrade;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    proxy_set_header </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Connection </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"upgrade"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    proxy_set_header </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Host $host;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    proxy_read_timeout </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">86400</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span></code></pre>
<p>With Caddy, <code>reverse_proxy localhost:3000</code> handles WebSockets automatically — no extra config needed.</p>
<p>Updates are clean. Your data lives in the mounted volumes and is never touched during a pull:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> compose</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> pull</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> &#x26;&#x26; </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> compose</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> up</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -d</span></span>
<span class="line"></span></code></pre>
<h2 id="honest-verdict">Honest Verdict</h2>
<p>For a project this young, the feature coverage is remarkable. SQLite keeps the operational footprint low — no Postgres to manage, backups are just file copies. The security defaults in the Compose example are better than most mature projects bother to include (read-only filesystem, dropped capabilities — that's not standard for a hobby project).</p>
<p>The rough edges are real. This is still a one-developer project — check the open issues on GitHub before deploying this for a group trip. Some configuration, like OIDC discovery URL overrides, assumes you already know your auth provider well. And at version 3.x, there's no migration path guarantee if a data schema changes under you.</p>
<p>But if you're the kind of person who already self-hosts Immich for photos and Actual Budget for finances, TREK is the obvious next piece. The &quot;why are you still using Google Sheets for this&quot; feeling hits fast.</p>
<h2 id="go-try-it">Go Try It</h2>
<p>Start with the one-liner Docker run to kick the tires. Once you decide it's worth keeping, migrate to the full Compose setup with an <code>.env</code> file for your encryption key.</p>
<p><strong>Source:</strong> <a href="https://github.com/mauriceboe/TREK">github.com/mauriceboe/TREK</a></p>
<!--kg-card-end: markdown-->]]></content:encoded>
  </item>
  <item>
    <title>no-mistakes: A Git Proxy That Validates Your Code Before It Leaves Your Machine</title>
    <link>https://justmeandlinux.com/no-mistakes-a-git-proxy-that-validates-your-code-before-it-leaves-your-machine/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/no-mistakes-a-git-proxy-that-validates-your-code-before-it-leaves-your-machine/</guid>
    <pubDate>Thu, 25 Jun 2026 10:01:45 GMT</pubDate>
    <category>Apps</category>
    <description>AI agents write code faster than humans can review it. no-mistakes puts a validation gate right in your git push — AI review, tests, lint, and auto-fixes — before anything reaches origin.</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>You're pushing branches faster than you can review them. That's the new reality. AI agents write code faster than humans read it, and the bottleneck has quietly shifted — it's no longer writing code, it's validating it before it lands somewhere public.</p>
<p><code>no-mistakes</code> is a local git proxy that intercepts your push before it reaches origin. You configure it once per repo, then push to the <code>no-mistakes</code> remote instead of <code>origin</code>. It spins up a disposable worktree, runs a validation pipeline — AI code review, tests, docs, lint, rebase onto fresh upstream — then forwards your branch and opens the PR automatically. Only if everything passes.</p>
<h2 id="what-it-actually-does">What It Actually Does</h2>
<p>The pipeline isn't just formatting. Here's what runs before anything reaches your remote:</p>
<ul>
<li><strong>Rebase</strong> onto fresh upstream (no merge conflicts landing in the PR)</li>
<li><strong>AI code review</strong> via whichever agent you have configured</li>
<li><strong>Tests</strong> — regression + new tests executed</li>
<li><strong>Documentation</strong> — keeps docs aligned with code changes</li>
<li><strong>Lint and formatting</strong> — done before push, not after</li>
<li><strong>PR creation</strong> — opened automatically once the branch is clean</li>
</ul>
<p>Safe, mechanical fixes get applied without asking. Anything that touches intent gets escalated to you. The human stays in the loop, but only for the decisions that actually need a human.</p>
<h2 id="installation">Installation</h2>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -fsSL</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> https://raw.githubusercontent.com/kunchenguid/no-mistakes/main/docs/install.sh</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> sh</span></span>
<span class="line"></span></code></pre>
<p>Windows, Go install, and build-from-source options are in their <a href="https://kunchenguid.github.io/no-mistakes/start-here/installation/">installation guide</a>.</p>
<h2 id="setup">Setup</h2>
<p>In your repo:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">no-mistakes</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> init</span></span>
<span class="line"></span></code></pre>
<p>This sets up a local git remote called <code>no-mistakes</code> and optionally installs the <code>/no-mistakes</code> skill for Claude Code and other agents. You'll see something like:</p>
<pre data-lang-none><code>✓ Gate initialized

  repo  /home/you/src/my-repo
  gate  no-mistakes → /home/you/.no-mistakes/repos/abc123def456.git
  remote  git@github.com:you/my-repo.git
  skill  /no-mistakes installed for agents at user level

Push through the gate with:
  git push no-mistakes &lt;branch&gt;
</code></pre>
<h2 id="using-it">Using It</h2>
<p>The swap is just the remote name:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">git</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> push</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> no-mistakes</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> my-branch</span></span>
<span class="line"></span></code></pre>
<p>Then run <code>no-mistakes</code> to open the TUI and watch the pipeline. Findings come up one by one — auto-fixable ones are handled for you (or you approve them), judgment calls are flagged for you to decide on. Once everything is green, the gate pushes your branch to origin and opens the PR.</p>
<p>If you're working with a coding agent, the agent skill makes this fully headless:</p>
<pre data-lang-none><code>/no-mistakes
</code></pre>
<p>The agent drives the entire flow — pipeline runs, fixes what it safely can, escalates the rest, pushes the branch.</p>
<p>For GitHub fork contributions, there's an extra flag:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">no-mistakes</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> init</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --fork-url</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">your-fork-ur</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">l</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span></span>
<span class="line"></span></code></pre>
<h2 id="the-honest-downsides">The Honest Downsides</h2>
<p>This is early software. A few things to know going in:</p>
<p><strong>Agent quality is the ceiling.</strong> The AI review step is only as good as the agent you've configured. A mediocre model running review is still a mediocre review — just earlier in the process. The gate amplifies your agent, it doesn't replace judgment.</p>
<p><strong>Configuration overhead.</strong> Per-repo setup is fast, but if you're across dozens of repos, <code>no-mistakes init</code> gets repetitive. There's no global default that just works everywhere.</p>
<p><strong>The pipeline isn't fully customizable yet.</strong> The steps run in a fixed order and with fixed logic. If your project has unusual test setups or build requirements, you may hit friction. Check their docs and GitHub issues before committing to it on something critical.</p>
<p><strong>It's a local process.</strong> The disposable worktree runs on your machine. If you close your laptop mid-push, you're interrupting the pipeline. Not a dealbreaker, but worth knowing.</p>
<h2 id="verdict">Verdict</h2>
<p>The core insight here is correct: quality infrastructure should live closer to where you're working, not only in the outer CI/CD loop after the branch is already public. <code>no-mistakes</code> acts on that insight with a clean implementation — isolated worktrees, non-blocking pipeline, works with the agents you're already using.</p>
<p>If you're shipping AI-generated code at any real velocity, this is worth the setup time. It's most valuable when you're doing rapid iteration with an agent and want something between &quot;push and pray&quot; and &quot;full code review every time.&quot;</p>
<p>It won't replace a thoughtful reviewer on your team. It will catch the obvious stuff before that reviewer ever sees the branch — which is exactly what you want.</p>
<h2 id="try-it">Try It</h2>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">no-mistakes</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> init</span></span>
<span class="line"></span></code></pre>
<p>Start there. Push a branch through the gate, watch what it catches. The project is at <a href="https://github.com/kunchenguid/no-mistakes">github.com/kunchenguid/no-mistakes</a> and the full docs are at <a href="https://kunchenguid.github.io/no-mistakes/">kunchenguid.github.io/no-mistakes</a>.</p>
<!--kg-card-end: markdown-->]]></content:encoded>
  </item>
  <item>
    <title>codebase-memory-mcp: A Knowledge Graph for Your Codebase in a Single Binary</title>
    <link>https://justmeandlinux.com/codebase-memory-mcp-a-knowledge-graph-for-your-codebase-in-a-single-binary/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/codebase-memory-mcp-a-knowledge-graph-for-your-codebase-in-a-single-binary/</guid>
    <pubDate>Tue, 23 Jun 2026 10:02:56 GMT</pubDate>
    <category>Apps</category>
    <description>Every AI coding assistant hits the same wall. You drop into a new codebase, ask something structural — &amp;quot;what calls this function?&amp;quot; or &amp;quot;what talks to this service?&amp;quot; — and the agent starts reading files. One by one. Burning tokens on…</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Every AI coding assistant hits the same wall. You drop into a new codebase, ask something structural — &quot;what calls this function?&quot; or &quot;what talks to this service?&quot; — and the agent starts reading files. One by one. Burning tokens on boilerplate it doesn't need, missing context it can't see because it never built a mental model of the whole thing.</p>
<p><code>codebase-memory-mcp</code> fixes that.</p>
<h2 id="what-it-is">What It Is</h2>
<p><code>codebase-memory-mcp</code> is an MCP server that parses your entire codebase using <a href="https://tree-sitter.github.io/tree-sitter/">tree-sitter</a> — the same parser generator used by Neovim, Helix, and GitHub's semantic code search — and builds a persistent knowledge graph of functions, classes, call chains, HTTP routes, and cross-service links. It then exposes 14 structured query tools so your coding agent can ask structural questions against that graph instead of re-reading files from scratch every session.</p>
<p>A quick clarification: MCP (Model Context Protocol) is Anthropic's standard for connecting external tools and data to AI models. Think of it as a plugin API. Your agent — Claude Code, Codex CLI, Gemini CLI, and others — can call into MCP servers to get structured results instead of relying entirely on reading files from disk. <code>codebase-memory-mcp</code> is one of those servers. It's the one that understands your entire codebase's structure.</p>
<p>It ships as a single static binary. No Docker, no runtime dependencies, no API keys, no setup ceremony.</p>
<h2 id="why-its-worth-your-time">Why It's Worth Your Time</h2>
<p>The numbers are concrete. Five structural queries cost roughly <strong>3,400 tokens</strong> via <code>codebase-memory-mcp</code> versus <strong>412,000 tokens</strong> via file-by-file exploration. That's a 99.2% reduction. On a medium codebase, that's the difference between a query that eats half your context window and one that barely registers.</p>
<p>What makes this different from code search or grep: it's a <strong>graph</strong>, not an index. You can trace who calls a function, map the blast radius of a change before you commit it, detect dead code, and visualize cross-service HTTP links. An agent can ask &quot;who calls ProcessOrder?&quot; and get a structured call chain back in under 10ms — not because it read ten files, but because that relationship was already indexed and persisted from the first time you ran it.</p>
<p>Tree-sitter produces an Abstract Syntax Tree — a structured representation of your code that understands syntax, not just text patterns. What <code>codebase-memory-mcp</code> adds on top is a relational model: nodes for Functions, Classes, Routes, and Files; edges for CALLS, IMPORTS, IMPLEMENTS, HTTP_CALLS, DATA_FLOWS, and more. That model is what lets you run Cypher-like graph queries against your codebase:</p>
<pre data-lang-none><code>MATCH (f:Function)-[:CALLS]-&gt;(g) WHERE f.name = 'main' RETURN g.name
</code></pre>
<p>Cypher is the query language made popular by Neo4j — it reads like SQL but for graphs. You describe the shape of the relationship you're looking for, and the graph engine finds it.</p>
<p>For 11 languages including Python, TypeScript, Go, Rust, and Java, the tool goes further with what they call <strong>Hybrid LSP</strong> — a C implementation of type-resolution algorithms compatible with pyright, gopls, and tsserver. That means it understands return types, generic substitution, and trait resolution. Structural analysis, not just symbol matching.</p>
<p>The semantic search is powered by bundled Nomic embeddings compiled directly into the binary. No Ollama, no API key, no external model. Vector search — the kind that finds &quot;functions that are conceptually similar&quot; rather than &quot;functions whose names match this pattern&quot; — is included at no extra cost or configuration.</p>
<h2 id="hands-on">Hands On</h2>
<p><strong>Install (macOS / Linux):</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -fsSL</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> https://raw.githubusercontent.com/DeusData/codebase-memory-mcp/main/install.sh</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> bash</span></span>
<span class="line"></span></code></pre>
<p><strong>On Arch (AUR):</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">yay</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -S</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> codebase-memory-mcp-bin</span></span>
<span class="line"></span></code></pre>
<p>The installer auto-detects your coding agents. Claude Code gets an entry in <code>~/.claude/.mcp.json</code>. It handles Codex CLI, Gemini CLI, VS Code, Zed, Aider, and others — including OpenClaw — automatically.</p>
<p>Restart your agent after install.</p>
<p><strong>Indexing a project:</strong></p>
<p>Tell your agent: &quot;Index this project.&quot; Or from the CLI:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">codebase-memory-mcp</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> cli</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> index_repository</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '{"repo_path": "/path/to/your/repo"}'</span></span>
<span class="line"></span></code></pre>
<p>The knowledge graph persists in <code>~/.cache/codebase-memory-mcp/</code> as a SQLite database. Once indexed, every subsequent session starts with the full graph — no re-indexing, no warm-up period.</p>
<p><strong>Enable automatic indexing:</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">codebase-memory-mcp</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> config</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> set</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> auto_index</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span></span>
<span class="line"></span></code></pre>
<p>With this on, new projects are indexed on first connection. Previously-indexed projects are watched with git-based change detection — it tracks what changed, not what didn't.</p>
<p><strong>Query from the CLI:</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Find all functions matching a name pattern</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">codebase-memory-mcp</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> cli</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> search_graph</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '{"name_pattern": ".*Handler.*", "label": "Function"}'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Trace who calls a function (and what it calls)</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">codebase-memory-mcp</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> cli</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> trace_path</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '{"function_name": "ProcessOrder", "direction": "both"}'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Map the blast radius of your uncommitted changes</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">codebase-memory-mcp</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> cli</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> detect_changes</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '{}'</span></span>
<span class="line"></span></code></pre>
<p>Benchmarks from the README (Apple M3 Pro — your mileage will vary, but these are in the right ballpark):</p>
<table>
<thead>
<tr>
<th>Operation</th>
<th>Time</th>
</tr>
</thead>
<tbody>
<tr>
<td>Linux kernel (28M LOC, 75K files) — full index</td>
<td>3 minutes</td>
</tr>
<tr>
<td>Django — full index</td>
<td>~6 seconds</td>
</tr>
<tr>
<td>Cypher query</td>
<td>&lt;1ms</td>
</tr>
<tr>
<td>Call path trace (depth=5)</td>
<td>&lt;10ms</td>
</tr>
</tbody>
</table>
<p>There's an optional graph visualization UI that serves a 3D interactive view at <code>localhost:9749</code>. If you want to actually see your codebase's call graph as a navigable graph, it's there:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -fsSL</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> https://raw.githubusercontent.com/DeusData/codebase-memory-mcp/main/install.sh</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> bash</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --ui</span></span>
<span class="line"></span></code></pre>
<h2 id="honest-verdict">Honest Verdict</h2>
<p>This is a well-engineered tool. The single binary philosophy, the bundled tree-sitter grammars for 158 languages, the bundled embeddings — whoever designed this understood that tool adoption dies at friction. The install works. The problem being solved is real. And unlike most &quot;AI tooling&quot; projects that appeared overnight and will be abandoned by August, this one has a published research paper behind it, benchmarks against real-world repos, and genuine technical depth.</p>
<p>Two honest caveats.</p>
<p>First: it reads your codebase and writes to your agent config files. The README is upfront about this. The source is auditable, every release binary is checksummed and scanned by 70+ antivirus engines, and all processing happens locally — your code never leaves your machine. But if you're working on sensitive code, audit the installer before you run it. That's not paranoia. It's just good hygiene for anything that touches your agent configs.</p>
<p>Second: Hybrid LSP type resolution covers 11 languages well. Outside that list, you get structural analysis only. Totally fine for most stacks. Worth knowing if yours is unusual.</p>
<p>What this is <strong>not</strong>: a replacement for reading code. For complex logic, tricky algorithms, or architectural decisions, your agent still needs to read source. What <code>codebase-memory-mcp</code> gives you is a dramatically cheaper first pass — find the right files, trace the dependencies, map the blast radius — before the expensive read operations. That's the workflow shift.</p>
<p>Who it's actually for: anyone running Claude Code, Codex CLI, or any MCP-compatible agent against a non-trivial codebase, frustrated with how much context gets burned on basic structural navigation. The bigger the repo, the bigger the payoff.</p>
<h2 id="go-try-it">Go Try It</h2>
<p>Install it. Index a real project you work on. Ask your agent something structural — &quot;what calls this function?&quot; or &quot;show me the architecture of this service.&quot; See whether it reads files or queries the graph.</p>
<p><strong>→ <a href="https://github.com/DeusData/codebase-memory-mcp">DeusData/codebase-memory-mcp on GitHub</a></strong></p>
<!--kg-card-end: markdown-->]]></content:encoded>
  </item>
  <item>
    <title>Headroom: Strip the Noise Before It Hits the LLM</title>
    <link>https://justmeandlinux.com/headroom-strip-the-noise-before-it-hits-the-llm/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/headroom-strip-the-noise-before-it-hits-the-llm/</guid>
    <pubDate>Sat, 20 Jun 2026 10:02:34 GMT</pubDate>
    <category>Apps</category>
    <description>Every AI agent you run is drowning in noise. Stack traces that are 80% whitespace. JSON responses with a thousand fields when you needed three. Logs padded with timestamps for lines you didn&apos;t ask about. You&apos;re paying for all of it. Headroom stops that before…</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Every AI agent you run is drowning in noise. Stack traces that are 80% whitespace. JSON responses with a thousand fields when you needed three. Logs padded with timestamps for lines you didn't ask about. You're paying for all of it. Headroom stops that before the LLM ever sees it.</p>
<h2 id="what-it-is">What It Is</h2>
<p>Headroom is a context compression layer that sits between your code — or your terminal, or your coding agent — and the LLM. It squeezes everything going in before the model ever touches it, then optionally trims what comes back out. It runs locally, your data stays on your machine, and it's reversible.</p>
<p>It ships in three forms: a Python/TypeScript library, a drop-in HTTP proxy (zero code changes), and an MCP server. The numbers on their GitHub: 10,144 tokens → 1,260 tokens on a code search task. 92% reduction. Same result. They publish benchmarks and methodology, and they're reproducible.</p>
<h2 id="why-its-different">Why It's Different</h2>
<p>Most &quot;token compression&quot; tools truncate. They throw away the tail of your context window and hope what you needed was near the top. Headroom routes by content type to a purpose-built compressor:</p>
<ul>
<li><strong>SmartCrusher</strong> handles JSON — collapses repetitive structure, keeps the signal</li>
<li><strong>CodeCompressor</strong> uses AST parsing for Python, JS, Go, Rust, Java, and C++ — it knows what's code and what's scaffold</li>
<li><strong>Kompress-base</strong> is a trained HuggingFace model for prose and unstructured text</li>
<li><strong>CacheAligner</strong> stabilizes your prompt prefixes so Anthropic and OpenAI's KV caches actually hit consistently (this alone saves money)</li>
</ul>
<p>It's also <strong>reversible</strong>. The originals are cached locally via something they call CCR (Compressed Context Retrieval). If the LLM needs the full version of something, it can ask for it. You're not throwing data away — you're parking it.</p>
<h2 id="hands-on">Hands On</h2>
<p>Install:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">pip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "headroom-ai[all]"</span></span>
<span class="line"></span></code></pre>
<p>The fastest path — zero code changes required — is proxy mode:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">headroom</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> proxy</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --port</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 8787</span></span>
<span class="line"></span></code></pre>
<p>Point your OpenAI-compatible client at <code>http://localhost:8787</code> instead of the upstream API. Every request gets compressed in transit. Nothing in your application code changes.</p>
<p>Or wrap a coding agent directly:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">headroom</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> wrap</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> claude</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">headroom</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> wrap</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> aider</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">headroom</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> wrap</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> codex</span></span>
<span class="line"></span></code></pre>
<p>After a session, check what you saved:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">headroom</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> perf</span></span>
<span class="line"></span></code></pre>
<p>If you want to reduce output tokens too — stop the model from restating context and writing ceremony — enable the output shaper:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">export</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> HEADROOM_OUTPUT_SHAPER</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">headroom</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> proxy</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --port</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 8787</span></span>
<span class="line"></span></code></pre>
<h2 id="the-honest-part">The Honest Part</h2>
<p>The full <code>[all]</code> install pulls in ML dependencies and downloads the Kompress-base model from HuggingFace on first run. That's a wait. If you just want the JSON and code compressors without the ML model, install with fewer extras and skip the download.</p>
<p>The output token reduction numbers are estimates with confidence intervals, not hard measurements — because you can't measure what the model <em>would</em> have written. Headroom is honest about this in the docs, which is worth noting. The input compression is measurable and real; the output side is best-effort.</p>
<p>It also requires Python 3.10+. If you're on an older system, that's a hurdle. And if you're behind SSL inspection, the ONNX runtime and HuggingFace model downloads can hit cert issues — the README covers workarounds, but it's not trivial.</p>
<h2 id="verdict">Verdict</h2>
<p>The proxy mode is genuinely zero-effort to adopt if you're already using an OpenAI-compatible client. The AST-aware code compression is the part I find most credible — that's not a heuristic, that's the compiler knowing where the boilerplate is. The JSON crusher is similarly solid.</p>
<p>Who it's for: anyone running AI coding agents daily who's watching token costs add up or hitting context limits. If you use Claude Code, Codex, or Aider and you're not compressing your context, you're burning money on noise.</p>
<p>Not worth the setup if you only run an LLM occasionally or you're fine with your current context window. But if you're running agents hard — this is one of the more thought-through tools I've seen tackle this problem.</p>
<h2 id="go-try-it">Go Try It</h2>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">pip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "headroom-ai[all]"</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">headroom</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> wrap</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> claude</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">   # or aider, codex, cursor</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">headroom</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> perf</span></span>
<span class="line"></span></code></pre>
<p>→ <a href="https://github.com/chopratejas/headroom">GitHub: chopratejas/headroom</a><br>
→ <a href="https://headroom-docs.vercel.app/docs">Docs</a></p>
<!--kg-card-end: markdown-->]]></content:encoded>
  </item>
  <item>
    <title>xan: a Rust CSV processor for the terminal</title>
    <link>https://justmeandlinux.com/xan-the-csv-tool-that-actually-keeps-up-with-you/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/xan-the-csv-tool-that-actually-keeps-up-with-you/</guid>
    <pubDate>Thu, 18 Jun 2026 10:01:41 GMT</pubDate>
    <category>Apps</category>
    <description>Most CSV tools either choke on big files or make you write Python. xan does neither. A SIMD-powered Rust CLI that handles gigabyte CSVs and renders histograms straight in your terminal.</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>CSV files are everywhere. Server exports, billing dumps, database snapshots, Grafana data. You can open them in Excel, or you can stay in the terminal and work at actual speed.</p>
<h2 id="what-it-is">What It Is</h2>
<p><code>xan</code> is a command-line CSV processor written in Rust. It started as a fork of the classic <code>xsv</code> by BurntSushi — but it's been so thoroughly rewritten at this point it's essentially its own tool. It uses a SIMD-accelerated CSV parser, can parallelize operations with multithreading, handles multi-gigabyte files without a sweat, and exposes a composable set of commands that feel like natural Unix extensions.</p>
<p>It also does something no other tool in this space does as cleanly: it can draw data visualizations directly in your terminal. Histograms, scatterplots, time series. No Python. No Jupyter. Pipe and go.</p>
<h2 id="why-its-worth-your-time">Why It's Worth Your Time</h2>
<p>Most CSV tools fail in one of two places. Tools like <code>csvkit</code> are Python-based and choke once files get large. <code>xsv</code> is fast but has been largely unmaintained. Reaching for <code>awk</code> or <code>pandas</code> for basic slicing and counting is overhead you don't need.</p>
<p><code>xan</code> threads the needle: fast by default, readable command names, and a small expression language (<code>moonblade</code>) for when the simple subcommands aren't enough. It also handles CSV-adjacent formats — you can feed it Excel files, JSON, VCF, SAM, and more via <code>xan from</code>.</p>
<p>The philosophy is pure Unix: every command reads from stdin, writes to stdout, and chains naturally with the rest of your pipeline.</p>
<h2 id="hands-on">Hands On</h2>
<p><strong>Install on Arch Linux:</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sudo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> pacman</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -S</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> xan</span></span>
<span class="line"></span></code></pre>
<p><strong>Or via Cargo on any distro:</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">cargo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> xan</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --locked</span></span>
<span class="line"></span></code></pre>
<p>Pre-built binaries are also available on the <a href="https://github.com/medialab/xan/releases/latest">releases page</a> if you'd rather not compile.</p>
<p><strong>Start by exploring a file:</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># What columns does this CSV have?</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">xan</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> headers</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> data.csv</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># How many rows?</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">xan</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> count</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> data.csv</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Pretty-print the first 10 rows</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">xan</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> view</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> data.csv</span></span>
<span class="line"></span></code></pre>
<p><code>xan view</code> renders a proper aligned table in your terminal. It truncates long values and shows you exactly how many columns exist. Much more useful than <code>head</code>.</p>
<p><strong>Filter and search:</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Find rows where a column matches a pattern</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">xan</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> search</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> status</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "^5"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> access.csv</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> xan</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> view</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Filter with an expression</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">xan</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> filter</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'count > 100'</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> stats.csv</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> xan</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> count</span></span>
<span class="line"></span></code></pre>
<p><strong>The histogram command is the killer feature:</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">xan</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> frequency</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> status_code</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> access.csv</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> xan</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> hist</span></span>
<span class="line"></span></code></pre>
<p>Output:</p>
<pre data-lang-none><code>Histogram for status_code (bars: 3, sum: 1247, max: 891):
200 |891 71.5%|━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━|
404 |247 19.8%|━━━━━━━━━━╸                              |
500 |109  8.7%|━━━━╸                                    |
</code></pre>
<p>That's a frequency distribution rendered in your terminal in milliseconds — even on files with millions of rows.</p>
<p><strong>Stats at a glance:</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">xan</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> stats</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> response_time</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> data.csv</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> xan</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> transpose</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> xan</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> view</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -I</span></span>
<span class="line"></span></code></pre>
<p>You get count, mean, stddev, min, max — all without leaving your shell.</p>
<p><strong>The expression language</strong> (<code>moonblade</code>) handles anything the simple commands can't:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Compute a derived column</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">xan</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> map</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'len(path) as path_length'</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> access.csv</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> xan</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> filter</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'path_length > 200'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Format values</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">xan</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> map</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'fmt("{} ({})", name, year) as label'</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> data.csv</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> xan</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> select</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> label</span></span>
<span class="line"></span></code></pre>
<p>The cheatsheet is one command away: <code>xan help cheatsheet</code>.</p>
<h2 id="honest-verdict">Honest Verdict</h2>
<p><code>xan</code> is a focused tool. It doesn't try to replace pandas or become a general-purpose data platform. That restraint is what keeps it fast and composable.</p>
<p>The rough edges: the expression language has a learning curve, and the docs — while solid — assume you're willing to read them. A few advanced subcommands are under-documented. If your CSV use case is simple, <code>xsv</code> still works fine. If your files are large, your logic is non-trivial, or you want terminal charts without spinning up a Python environment, <code>xan</code> earns its place in your toolkit.</p>
<p>Run it once on a real dataset. The speed will make the decision for you.</p>
<h2 id="go-try-it">Go Try It</h2>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">cargo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> xan</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --locked</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">xan</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --help</span></span>
<span class="line"></span></code></pre>
<p>Then skim the <a href="https://github.com/medialab/xan#quick-tour">quick tour in the README</a> — it walks through twenty real commands on a real dataset.</p>
<p><strong>Project:</strong> <a href="https://github.com/medialab/xan">github.com/medialab/xan</a></p>
<!--kg-card-end: markdown-->]]></content:encoded>
  </item>
  <item>
    <title>Kage: Clone Any Website for Offline Reading, JavaScript-Free</title>
    <link>https://justmeandlinux.com/kage-clone-any-website-for-offline-reading-javascript-free/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/kage-clone-any-website-for-offline-reading-javascript-free/</guid>
    <pubDate>Tue, 16 Jun 2026 10:04:17 GMT</pubDate>
    <category>Apps</category>
    <description>Save As has been lying to you for decades. Kage clones websites the right way — renders with real Chrome, strips all JavaScript, and packs the result into files you can actually keep.</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>&quot;Save As&quot; has been lying to you for decades. You click it on a page you want to keep. Six months later you open it and find a blank screen, a spinner that never resolves, or a &quot;live&quot; copy still phoning home to analytics servers that have moved on without you. The page was never really yours — it was a thin client for someone else's JavaScript.</p>
<p>Kage takes the other road.</p>
<h2 id="what-it-is">What It Is</h2>
<p>Kage (影, &quot;shadow&quot;) is a Go CLI that clones websites for offline reading the right way. It drives real headless Chrome, waits for the page to finish doing whatever it does, captures the final DOM a human would actually see, then strips every <code>&lt;script&gt;</code> tag out. CSS, images, and fonts come down to local paths. What lands on disk looks like the live site and runs no code.</p>
<p>No tracking. No network calls. No surprises. Just <code>.html</code> files you can open off disk, hand to a friend, or pack into a single file and forget about for a decade.</p>
<p><strong>Project:</strong> <a href="https://github.com/tamnd/kage">github.com/tamnd/kage</a></p>
<h2 id="why-its-worth-your-time">Why It's Worth Your Time</h2>
<p>Most offline archival tools fall into one of two failure modes: they either can't handle JS-rendered pages and save empty shells, or they save something that still depends on remote servers for half its assets. <code>wget --mirror</code> will happily clone a site and leave you with hundreds of pages that load nothing because the CSS was served from a CDN it didn't follow.</p>
<p>Kage's approach is different. It lets the browser do the full render — JavaScript, dynamic content, lazy images — and then amputates the scripting layer. The finished HTML stays. The code that generated it doesn't.</p>
<p>Two features stand out. First, <code>kage pack</code> can compress an entire mirror into a single self-executing binary. Run <code>./paulgraham</code> on any machine and it serves itself. No dependencies, no installation required. Second, <code>--scope-prefix</code> keeps crawls surgical — point it at <code>/doc/</code> and it won't spider the rest of the site. Most crawlers will happily index half the internet if you let them.</p>
<h2 id="hands-on">Hands On</h2>
<p>Kage needs Chrome or Chromium on the host to drive the rendering. If you'd rather not install it, the Docker image bundles Chromium and needs nothing else:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Docker — no local Chrome required</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> run</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --rm</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -v</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">$PWD</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/out:/out"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ghcr.io/tamnd/kage</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> clone</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> paulgraham.com</span></span>
<span class="line"></span></code></pre>
<p>Installing directly with Go:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">go</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> github.com/tamnd/kage/cmd/kage@latest</span></span>
<span class="line"></span></code></pre>
<p>Prebuilt <code>.deb</code>, <code>.rpm</code>, and static binaries are available on <a href="https://github.com/tamnd/kage/releases">the releases page</a> if you'd rather skip the Go toolchain.</p>
<p>Basic workflow:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Clone a site (saves to ~/data/kage/paulgraham.com/ by default)</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kage</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> clone</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> paulgraham.com</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Serve it back locally</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kage</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> serve</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ~/data/kage/paulgraham.com</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># open http://127.0.0.1:8800</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Partial clone — first 50 pages, 2 links deep</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kage</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> clone</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> paulgraham.com</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --max-pages</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 50</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --max-depth</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 2</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Only a specific section of a site</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kage</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> clone</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> go.dev</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --scope-prefix</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /doc</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Scroll pages to trigger lazy-loaded images</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kage</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> clone</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> example.com</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --scroll</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Re-render in place to pick up new content</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kage</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> clone</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> paulgraham.com</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --refresh</span></span>
<span class="line"></span></code></pre>
<p>Packing for distribution:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Single shareable ZIM archive</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kage</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> pack</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> paulgraham.com</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># → paulgraham.com.zim</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Self-contained executable that IS the site</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kage</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> pack</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> paulgraham.com</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --format</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> binary</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -o</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> paulgraham</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">./paulgraham</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  # serves itself, needs nothing installed</span></span>
<span class="line"></span></code></pre>
<p>The ZIM format is the same one used by Kiwix — a mature offline reading ecosystem with apps for Android, iOS, and desktop. Worth knowing if you want to browse the archive on mobile.</p>
<h2 id="honest-verdict">Honest Verdict</h2>
<p>The Chrome dependency is the obvious friction point. On a headless server, <code>apt install chromium</code> works fine, but it's not nothing. The Docker image sidesteps this cleanly if containers are part of your workflow.</p>
<p>Auth-gated content is out of scope unless you can pre-authenticate a Chrome session and hand it off. Heavy SPAs with complex client-side routing will need tuning; <code>--scroll</code> helps with lazy loading but it can't work around apps that render entire routes in JavaScript with no server-side HTML.</p>
<p>For documentation sites, essays, technical blogs, reference material — anything static or close to it — this is exactly the right tool. The core idea is sound, the execution is clean, and the &quot;self-executing binary&quot; trick alone makes it worth bookmarking.</p>
<h2 id="go-try-it">Go Try It</h2>
<p>The Docker path is the lowest-friction start — no Chrome install, no Go toolchain, just a URL:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> run</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --rm</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -v</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">$PWD</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/out:/out"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ghcr.io/tamnd/kage</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> clone</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">your-site-her</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">e</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span></span>
<span class="line"></span></code></pre>
<p>Full documentation at <a href="https://kage.tamnd.com">kage.tamnd.com</a>.</p>
<!--kg-card-end: markdown-->]]></content:encoded>
  </item>
  <item>
    <title>GitHub Copilot CLI: The Terminal Finally Got Smart</title>
    <link>https://justmeandlinux.com/github-copilot-cli-the-terminal-finally-got-smart/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/github-copilot-cli-the-terminal-finally-got-smart/</guid>
    <pubDate>Sat, 18 Apr 2026 10:01:23 GMT</pubDate>
    <category>Apps</category>
    <description>GitHub Copilot CLI: The Terminal Finally Got Smart GitHub just shipped Copilot CLI to general availability, and it&apos;s not the toy you might expect. This is AI that actually helps instead of getting in your way. What It Actually Is It&apos;s an extension for GitHub…</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="github-copilot-cli-the-terminal-finally-got-smart">GitHub Copilot CLI: The Terminal Finally Got Smart</h2>
<p>GitHub just shipped Copilot CLI to general availability, and it's not the toy you might expect. This is AI that actually helps instead of getting in your way.</p>
<h2 id="what-it-actually-is">What It Actually Is</h2>
<p>It's an extension for GitHub CLI that translates natural language into shell commands. Ask it &quot;find all Python files modified in the last week&quot; and it gives you the find command with all the right flags. Ask it to explain a gnarly pipeline you found in a script, and it breaks down what each part does.</p>
<p>But the real story is what they added for GA: Autopilot mode. Instead of asking permission for every command, it can chain operations together — run a build, check the output, adjust the approach, keep going. It's the difference between a helpful suggester and something that can actually complete tasks.</p>
<h2 id="why-its-worth-your-time">Why It's Worth Your Time</h2>
<p>Most AI coding tools live in your editor. This one lives where you actually run things. That matters more than you'd think.</p>
<p>I've been testing it for the past week. The command suggestions are solid — it nailed complex git operations, Docker compose setups, and SSH tunneling that would have sent me to Stack Overflow. The explain feature is even better. Point it at any command and get a plain-English breakdown. No more reverse-engineering what that regex in your colleague's script actually does.</p>
<p>The new Autopilot mode is where it gets interesting. Tell it &quot;set up a development environment for this React project&quot; and it'll check dependencies, install what's missing, configure your editor, and validate everything works. You review each step, but you don't have to babysit.</p>
<h2 id="hands-on">Hands On</h2>
<p>Installation is straightforward if you already have GitHub CLI:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">gh</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> extension</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> github/gh-copilot</span></span>
<span class="line"></span></code></pre>
<p>Two main commands: <code>gh copilot suggest</code> for getting commands and <code>gh copilot explain</code> for understanding them.</p>
<p>The suggest mode feels natural. Type <code>gh copilot suggest &quot;compress this folder but exclude node_modules&quot;</code> and it offers a tar command with the right exclusion flags. Hit enter to run it or edit first.</p>
<p>The explain mode is where I found myself using it most. Paste any command — even ones with pipes, xargs, and complex flags — and get a breakdown. It explained a Docker cleanup script that I'd been copy-pasting for months without really understanding.</p>
<p>Autopilot mode requires the <code>--autopilot</code> flag and works best for multi-step tasks. The AI runs commands, checks results, and adapts. It's not perfect — it occasionally goes down rabbit holes — but when it works, it saves real time.</p>
<p>The rough edges: it sometimes suggests overly verbose commands when simpler ones exist. The autopilot mode can be chatty, explaining every decision when you just want results. And like all AI tools, it occasionally hallucinates flags that don't exist.</p>
<h2 id="honest-verdict">Honest Verdict</h2>
<p>This isn't replacing your terminal skills. But it's the first AI tool that actually feels useful in day-to-day command line work.</p>
<p>The competition exists — Warp rebuilt the terminal around AI, shell-gpt gives you local models, aichat offers similar command suggestions for free. But GitHub's integration with their ecosystem and the new autonomous features make this worth the subscription cost if you're already paying for Copilot.</p>
<p>It's not perfect. The autopilot mode needs guardrails, and the suggestions occasionally miss obvious solutions. But the core idea is sound: AI that helps you execute, not just plan.</p>
<h2 id="go-try-it">Go Try It</h2>
<p>If you have GitHub Copilot already, install the extension and try the explain feature on commands you run regularly. Start there. The suggest mode takes more trust, but it's worth building up to.</p>
<p>The terminal was the last major development surface without decent AI integration. That just changed.</p>
<hr>
<p><em>Compiled by AI. Proofread by caffeine. ☕</em></p>
<!--kg-card-end: markdown-->]]></content:encoded>
  </item>
  <item>
    <title>zoxide: a cd command that learns your directories</title>
    <link>https://justmeandlinux.com/zoxide-the-cd-command-that-actually-remembers/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/zoxide-the-cd-command-that-actually-remembers/</guid>
    <pubDate>Thu, 16 Apr 2026 10:01:55 GMT</pubDate>
    <category>Apps</category>
    <description>A smart replacement for cd that remembers which directories you use most frequently, letting you jump to them in just a few keystrokes.</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>You type <code>cd</code> a dozen times a day. Navigate to deeply nested directories, type the same paths over and over, fight with tab completion that doesn't know where you actually want to go. There has to be a better way.</p>
<p>There is. It's called zoxide, and it learns.</p>
<h2 id="what-it-is">What It Is</h2>
<p>zoxide is a replacement for cd that tracks your directory usage and ranks paths by frequency and recency. Instead of typing full paths, you give it fragments of where you want to go. It figures out the rest.</p>
<p>After a week of use, <code>z proj</code> takes you to <code>/home/user/development/projects/current</code>. <code>z conf</code> jumps to <code>/etc/nginx/conf.d</code>. <code>z log</code> lands you in <code>/var/log/nginx</code> or <code>/home/user/.config/app/logs</code> depending on which you visit more.</p>
<p>It's written in Rust, inspired by z and autojump, but faster and with better defaults.</p>
<h2 id="why-its-worth-your-time">Why It's Worth Your Time</h2>
<p>Most &quot;smart cd&quot; tools require you to learn their quirks. zoxide just works. The matching algorithm is intuitive — you can use partial paths, multiple keywords, even regex when needed. <code>z foo bar</code> finds directories containing both &quot;foo&quot; and &quot;bar&quot; anywhere in the path.</p>
<p>But here's what sold me: the interactive mode. <code>zi</code> (zoxide + fzf) shows you a fuzzy-searchable list of matches. No guessing, no typing fragments and hoping. You see your options, pick the right one, done.</p>
<p>The tool supports every shell worth using — bash, zsh, fish, PowerShell, even Nushell. Installation is dead simple on every platform. One command, add a line to your shell config, restart. That's it.</p>
<h2 id="hands-on">Hands On</h2>
<p>Installation is straightforward:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Linux/macOS</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -sSfL</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> sh</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Or use your package manager</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Ubuntu/Debian: apt install zoxide</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Arch: pacman -S zoxide  </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># macOS: brew install zoxide</span></span>
<span class="line"></span></code></pre>
<p>Add to your shell (for zsh, add to ~/.zshrc):</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">eval</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "$(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">zoxide</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> init zsh)"</span></span>
<span class="line"></span></code></pre>
<p>Restart your shell. Now use <code>cd</code> normally for a few days. zoxide watches and learns silently.</p>
<p>After it has some data, the magic begins:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">z</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> downloads</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    # Jump to most-used downloads directory</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">z</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> proj</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> web</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">     # Find project containing "web"</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">zi</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">             # Interactive selection with fzf</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">z</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ..</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">           # Works like regular cd too</span></span>
<span class="line"></span></code></pre>
<p>The more you use it, the smarter it gets. Directories you visit daily rise to the top. Paths you haven't touched in months fade into the background.</p>
<p>You can import data from other tools if you're already using autojump, z, or fasd. The migration is painless.</p>
<h2 id="honest-verdict">Honest Verdict</h2>
<p>This is one of those tools that makes you wonder how you worked without it. The learning curve is zero — you use <code>z</code> exactly like <code>cd</code> until you discover the shortcuts. The performance is excellent. The integrations with file managers and editors mean the benefits extend beyond the terminal.</p>
<p>The only downside: you'll get frustrated using other people's machines that don't have it installed. That's a good problem to have.</p>
<p>zoxide is for anyone who spends time in the terminal and values efficiency over typing. If you navigate directories more than occasionally, install it today. You'll thank yourself by next week.</p>
<h2 id="go-try-it">Go Try It</h2>
<p>Start with the basic installation. Use <code>cd</code> normally for a few days to build up the database. Then try <code>z</code> instead of <code>cd</code> with partial directory names.</p>
<p>Once you see how well it works, install fzf for the interactive <code>zi</code> command. That's where the real magic happens.</p>
<p><strong>GitHub:</strong> <a href="https://github.com/ajeetdsouza/zoxide">https://github.com/ajeetdsouza/zoxide</a><br>
<strong>Installation:</strong> curl -sSfL <a href="https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh">https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh</a> | sh</p>
<hr>
<p><em>Compiled by AI. Proofread by caffeine. ☕</em></p>
<!--kg-card-end: markdown-->]]></content:encoded>
  </item>
  <item>
    <title>Claude Code: Anthropic&apos;s agentic coding assistant in the terminal</title>
    <link>https://justmeandlinux.com/claude-code-ai-that-actually-codes-in-your-terminal/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/claude-code-ai-that-actually-codes-in-your-terminal/</guid>
    <pubDate>Thu, 09 Apr 2026 10:03:08 GMT</pubDate>
    <category>Apps</category>
    <description>Claude Code: AI That Actually Codes in Your Terminal A coding assistant that runs commands, edits files, and handles git workflows without leaving your shell. That&apos;s the promise. Here&apos;s whether it delivers. What Claude Code Actually Is It&apos;s Anthropic&apos;s…</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="claude-code-ai-that-actually-codes-in-your-terminal">Claude Code: AI That Actually Codes in Your Terminal</h2>
<p>A coding assistant that runs commands, edits files, and handles git workflows without leaving your shell. That's the promise. Here's whether it delivers.</p>
<h2 id="what-claude-code-actually-is">What Claude Code Actually Is</h2>
<p>It's Anthropic's terminal-based AI coding tool — think ChatGPT for code, but it can actually touch your files and run your commands. Point it at a project and it understands your codebase structure, dependencies, and git history. Then you tell it what you want in plain English, and it writes code, runs tests, commits changes, whatever you asked for.</p>
<p>The key difference from browser-based AI tools: Claude Code lives in your project directory. It sees your actual files, not just the snippets you paste. It can execute the commands it suggests instead of just telling you to run them.</p>
<h2 id="why-this-matters">Why This Matters</h2>
<p>Most AI coding tools make you play copy-paste tennis. You ask a question, get code back, paste it into your editor, realize it doesn't work with your setup, go back to the AI, explain the error, get a fix, repeat until something works.</p>
<p>Claude Code skips that loop. It sees the error when the command fails. It knows what packages you have installed. It can read your config files to understand your build setup. When it suggests a fix, it can test that fix immediately.</p>
<p>The workflow becomes conversational: &quot;Add user authentication to this Flask app&quot; and watch it install dependencies, create database migrations, write the routes, update your templates, and run the tests. If something breaks, it sees the error and fixes it without you having to explain what went wrong.</p>
<h2 id="installation-and-first-run">Installation and First Run</h2>
<p>Installation is straightforward on Linux and macOS:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -fsSL</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> https://claude.ai/install.sh</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> bash</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">cd</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> your-project</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">claude</span></span>
<span class="line"></span></code></pre>
<p>On Windows, you'll need Git for Windows first, then:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="powershell"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">irm https:</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">//</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">claude.ai</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">/</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">install.ps1 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> iex</span></span>
<span class="line"></span></code></pre>
<p>The first run prompts for authentication — you'll need either a Claude subscription ($20/month) or an Anthropic Console account with API credits. The CLI also supports third-party providers if you want to use a different model.</p>
<p>Once authenticated, you're in a conversational interface. Type what you want to accomplish, and Claude Code gets to work. It shows you exactly what it's planning to do before executing anything destructive.</p>
<h2 id="the-reality-check">The Reality Check</h2>
<p><strong>What it gets right:</strong> Claude Code actually understands project context in a way that feels natural. Ask it to &quot;refactor this component to use hooks&quot; and it knows which component you mean from recent files you've edited. It handles multi-file changes smoothly — updating imports when it moves functions, adjusting tests when it changes APIs.</p>
<p><strong>What's rough:</strong> The subscription requirement means this isn't a casual tool. At $20/month minimum, it needs to save you significant time to justify the cost. Free alternatives like GitHub Copilot ($10/month) or Codeium (free) don't have the same terminal integration, but they cover basic autocomplete and code generation.</p>
<p>The AI occasionally gets overconfident with unfamiliar frameworks or complex refactoring tasks. It's smart enough to read documentation and examples, but it can still hallucinate APIs that don't exist or miss subtle edge cases in your specific setup.</p>
<p><strong>Privacy consideration:</strong> Everything you do with Claude Code gets sent to Anthropic's servers — your code, your file structure, your commands. They have retention limits and don't use it for training, but if you work with sensitive codebases, this is a non-starter.</p>
<h2 id="the-honest-verdict">The Honest Verdict</h2>
<p>Claude Code works best as a pair programming partner for routine tasks. It's genuinely useful for boilerplate generation, test writing, debugging obvious errors, and handling git workflows. The terminal integration makes these interactions feel natural instead of like wrestling with a chatbot.</p>
<p>But it's not magic. Complex architectural decisions, performance optimizations, and domain-specific logic still require human judgment. Think of it as a very competent junior developer who never gets tired of writing boilerplate but occasionally needs supervision on the important stuff.</p>
<p>The pricing puts it in &quot;professional tool&quot; territory. If you're building software for a living and regularly wrestling with setup tasks, dependency management, or repetitive coding patterns, the time savings justify the cost. For hobby projects or occasional coding, stick with free alternatives.</p>
<h2 id="get-started">Get Started</h2>
<p>Visit <a href="https://claude.com/product/claude-code">claude.com</a> to sign up, then install the CLI. Start with a small project you know well — something where you can easily spot if the AI makes mistakes.</p>
<p>Try asking it to add a simple feature or fix a specific bug. Pay attention to how it explains its plan before executing. This is where Claude Code shines: showing its work and letting you correct course before it makes changes you can't easily undo.</p>
<p>The terminal is where real development happens. Having an AI that can actually participate in that environment, instead of just watching from the sidelines, makes the difference between a novelty and a tool.</p>
<hr>
<p><em>Compiled by AI. Proofread by caffeine. ☕</em></p>
<!--kg-card-end: markdown-->]]></content:encoded>
  </item>
  <item>
    <title>BentoPDF: a PDF toolkit that never uploads your documents</title>
    <link>https://justmeandlinux.com/bentopdf-the-pdf-toolkit-that-actually-gets-privacy-right/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/bentopdf-the-pdf-toolkit-that-actually-gets-privacy-right/</guid>
    <pubDate>Tue, 07 Apr 2026 10:01:29 GMT</pubDate>
    <category>Apps</category>
    <description>BentoPDF: The PDF Toolkit That Actually Gets Privacy Right I&apos;ve watched the PDF toolkit space for years. Most tools either send your documents to mysterious servers or consume resources like they&apos;re mining cryptocurrency. BentoPDF flips this on its head …</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="bentopdf-the-pdf-toolkit-that-actually-gets-privacy-right">BentoPDF: The PDF Toolkit That Actually Gets Privacy Right</h2>
<p>I've watched the PDF toolkit space for years. Most tools either send your documents to mysterious servers or consume resources like they're mining cryptocurrency. BentoPDF flips this on its head — everything happens in your browser, locally, with zero server processing.</p>
<h2 id="what-bentopdf-is">What BentoPDF Is</h2>
<p>A client-side PDF toolkit with 50+ tools that runs entirely in JavaScript. Merge, split, edit, convert — the usual suspects. But here's what's different: your files never leave your machine. Not &quot;encrypted in transit&quot; or &quot;temporarily processed server-side.&quot; They literally never leave your browser.</p>
<h2 id="why-its-worth-your-time">Why It's Worth Your Time</h2>
<p>Stirling PDF has been the go-to self-hosted solution, but it comes with baggage. Server resources, Java overhead, and recent telemetry controversies. BentoPDF solves the right problem: why should manipulating PDFs require a server at all?</p>
<p>The resource difference is striking. While Stirling PDF can crash a Raspberry Pi 4 under load, BentoPDF runs smooth on the same hardware because it's just serving static files. Your browser does the heavy lifting.</p>
<h2 id="hands-on">Hands On</h2>
<p>Getting started takes minutes:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Docker (simplest approach)</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> run</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -d</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -p</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 8080:8080</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --name</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> bentopdf</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> bentopdf/bentopdf:latest</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Or Docker Compose for persistent setup</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -o</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> docker-compose.yml</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> https://raw.githubusercontent.com/alam00000/bentopdf/main/docker-compose.yml</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">docker-compose</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> up</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -d</span></span>
<span class="line"></span></code></pre>
<p>The interface loads fast — no Java warm-up delays. Drag a PDF in, and you're immediately working with it. Need to merge five PDFs? Drop them in order, click merge, download. Done in seconds.</p>
<p>The drag-and-drop experience is noticeably better than Stirling's form-based approach. Reordering pages feels natural. Split tools let you visually select ranges instead of typing page numbers.</p>
<p>What surprised me: the conversion tools actually work well. PDF to Word maintains formatting better than expected. OCR runs locally using Tesseract.js — not perfect, but respectable for something happening entirely in your browser.</p>
<p>The offline mode is genuine. Disconnect from the internet, refresh the page, everything still works. That's rare for web apps these days.</p>
<h2 id="honest-verdict">Honest Verdict</h2>
<p>BentoPDF won't replace every PDF workflow, but it covers 90% of common tasks with zero infrastructure drama. The client-side approach means it scales infinitely (within your browser's memory limits) and you can trust it with sensitive documents.</p>
<p>The project is actively developed with regular releases. Documentation is thorough. The dual licensing (AGPL-3.0 for open source, commercial for business) is fair.</p>
<p>If you're running Stirling PDF just for basic document manipulation, BentoPDF eliminates server overhead entirely. If you need advanced features like form editing or digital signatures, Stirling still has an edge — for now.</p>
<p>For home labs, small offices, or anyone who values privacy over feature completeness, this is the better choice.</p>
<h2 id="go-try-it">Go Try It</h2>
<p>Skip the local setup and test drive it at <a href="https://bentopdf.com">bentopdf.com</a> first. Upload a test PDF and see how it feels. If you like it, self-host with the Docker command above.</p>
<p>The entire project lives at <a href="https://github.com/alam00000/bentopdf">github.com/alam00000/bentopdf</a> with solid documentation for static hosting options.</p>
<p>Sometimes the simplest approach wins. BentoPDF proves you don't need a server to solve server problems.</p>
<!--kg-card-end: markdown-->]]></content:encoded>
  </item>
  <item>
    <title>OpenScreen: Screen Studio Without the Subscription</title>
    <link>https://justmeandlinux.com/openscreen-screen-studio-without-the-subscription/</link>
    <guid isPermaLink="true">https://justmeandlinux.com/openscreen-screen-studio-without-the-subscription/</guid>
    <pubDate>Sat, 04 Apr 2026 10:09:46 GMT</pubDate>
    <category>Apps</category>
    <description>OpenScreen: Screen Studio Without the Subscription Screen recording tools charge like luxury software but deliver basic functionality. Screen Studio wants $29/month for what amounts to recording your screen with some polish. OpenScreen gives you the same core…</description>
    <dc:creator>Nour</dc:creator>
    <content:encoded><![CDATA[<h2 id="openscreen-screen-studio-without-the-subscription">OpenScreen: Screen Studio Without the Subscription</h2>
<p>Screen recording tools charge like luxury software but deliver basic functionality. Screen Studio wants $29/month for what amounts to recording your screen with some polish. OpenScreen gives you the same core features for exactly zero dollars.</p>
<h2 id="what-it-actually-is">What It Actually Is</h2>
<p>OpenScreen is an Electron app that records your screen and adds the visual flair people expect from demo videos. Automatic zoom-ins when you click things. Motion blur for smooth transitions. Background replacement so your messy desktop doesn't show. The stuff that makes your recorded walkthrough look intentional instead of accidental.</p>
<p>It's built by someone who got tired of subscription fees for screen recording. Not a 1:1 Screen Studio clone — it's simpler, more focused, and completely free.</p>
<h2 id="why-its-worth-your-time">Why It's Worth Your Time</h2>
<p>Most screen recorders give you raw footage and call it done. OpenScreen understands that demos need to look professional without requiring video editing skills. The automatic zoom feature alone saves hours of post-production work.</p>
<p>No watermarks. No trial periods. No "upgrade to export in 4K" nonsense. MIT licensed, so you can use it commercially, modify it, or build something else on top of it.</p>
<p>The developer is refreshingly honest about what this isn't: "If you need all the fancy features, your best bet is to support Screen Studio." That kind of honesty tells you everything about their priorities.</p>
<h2 id="hands-on">Hands On</h2>
<p>Download the installer from GitHub releases. On macOS, you'll need to bypass Gatekeeper since it's not signed:</p>
<pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e" tabindex="0" data-lang="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">xattr</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -rd</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> com.apple.quarantine</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /Applications/Openscreen.app</span></span>
<span class="line"></span></code></pre>
<p>Then grant screen recording and accessibility permissions in System Preferences. Standard dance for any serious screen capture tool.</p>
<p>The interface is clean. Hit record, choose your target (full screen or specific window), and it starts capturing. The magic happens in post — drag to add zoom points on your timeline, adjust the timing, swap backgrounds. The motion blur makes everything feel smooth even when you're frantically clicking through interfaces.</p>
<p>Audio capture works on Windows out of the box, needs macOS 13+ on Mac, and requires PipeWire on Linux (which most modern distros ship with).</p>
<h2 id="honest-verdict">Honest Verdict</h2>
<p>This hits the sweet spot between QuickTime's simplicity and Loom's feature bloat. It's not as polished as Screen Studio, but it's also not trying to extract $350/year from your wallet.</p>
<p>Perfect for product demos, tutorial recordings, and bug reports that actually help your team understand what went wrong. The automatic zoom feature alone makes this worth installing.</p>
<p>The biggest limitation? It's still beta software from a solo developer. Expect some rough edges and slower feature development compared to funded alternatives.</p>
<p>But here's the thing — it already does what most people actually need from a demo recording tool. Everything else is just marketing.</p>
<h2 id="go-try-it">Go Try It</h2>
<p>Download it from <a href="https://github.com/siddharthvaddem/openscreen/releases">GitHub releases</a>. Start with a simple window recording to test the waters. The automatic zoom will click immediately once you see it in action.</p>
<hr>
<p>Compiled by AI. Proofread by caffeine. ☕</p>]]></content:encoded>
  </item>
</channel>
</rss>