<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>x2q</title>
    <subtitle>x2q is a personal blog with notes on software, Linux, networking, payments, home labs, cooking, and the odd side project. Online since 2010.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://www.x2q.net/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://www.x2q.net"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-04-22T00:00:00+00:00</updated>
    <id>https://www.x2q.net/atom.xml</id>
    <entry xml:lang="en">
        <title>bchunk — convert .bin&#x2F;.cue images to .iso and .wav on Linux and macOS</title>
        <published>2026-04-22T00:00:00+00:00</published>
        <updated>2026-04-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.x2q.net/post/bchunk-bin-cue-to-iso-wav/"/>
        <id>https://www.x2q.net/post/bchunk-bin-cue-to-iso-wav/</id>
        
        <content type="html" xml:base="https://www.x2q.net/post/bchunk-bin-cue-to-iso-wav/">&lt;p&gt;&lt;strong&gt;TL;DR —&lt;&#x2F;strong&gt; &lt;strong&gt;&lt;code&gt;bchunk&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — sometimes called &lt;strong&gt;&lt;code&gt;binchunker&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — is a small, old, Unix command-line tool that takes a &lt;code&gt;.bin&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;.cue&lt;&#x2F;code&gt; pair (a raw CD image with a cue sheet) and splits it into a proper &lt;strong&gt;&lt;code&gt;.iso&lt;&#x2F;code&gt; file for the data track&lt;&#x2F;strong&gt; and one &lt;strong&gt;&lt;code&gt;.wav&lt;&#x2F;code&gt; file per audio track&lt;&#x2F;strong&gt;. It’s the right tool when a download or archive gave you &lt;code&gt;game.bin&lt;&#x2F;code&gt; + &lt;code&gt;game.cue&lt;&#x2F;code&gt; and you actually want to &lt;strong&gt;mount the data track&lt;&#x2F;strong&gt; or &lt;strong&gt;re-burn the audio tracks&lt;&#x2F;strong&gt;. Install with &lt;code&gt;apt install bchunk&lt;&#x2F;code&gt; on Debian&#x2F;Ubuntu, &lt;code&gt;brew install bchunk&lt;&#x2F;code&gt; on macOS, or build from source on anything else. Usage is &lt;code&gt;bchunk [options] image.bin image.cue basename&lt;&#x2F;code&gt; — you get &lt;code&gt;basename01.iso&lt;&#x2F;code&gt;, &lt;code&gt;basename02.wav&lt;&#x2F;code&gt;, &lt;code&gt;basename03.wav&lt;&#x2F;code&gt;, and so on.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-bin-cue-is-and-why-you-d-want-to-split-it&quot;&gt;What .bin&#x2F;.cue is, and why you’d want to split it&lt;&#x2F;h2&gt;
&lt;p&gt;A &lt;code&gt;.bin&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;.cue&lt;&#x2F;code&gt; pair is a &lt;strong&gt;raw, bit-accurate CD image&lt;&#x2F;strong&gt; plus a plain-text sheet that describes the track layout. The &lt;code&gt;.bin&lt;&#x2F;code&gt; contains every sector of the CD back-to-back — including data tracks (Mode 1 or Mode 2), audio tracks (raw Red Book PCM, 2352 bytes per sector, no filesystem), and the gaps between them. The &lt;code&gt;.cue&lt;&#x2F;code&gt; tells you where each track starts and what type it is.&lt;&#x2F;p&gt;
&lt;p&gt;This format is the standard output of CD-ripping tools like cdrdao and of old game-preservation scrapes, because it preserves &lt;em&gt;everything&lt;&#x2F;em&gt; — pregaps, CD-TEXT, mixed-mode CDs, CD-DA audio. That’s also why you can’t just rename &lt;code&gt;game.bin&lt;&#x2F;code&gt; to &lt;code&gt;game.iso&lt;&#x2F;code&gt; and mount it: a mixed-mode CD has &lt;strong&gt;non-filesystem sectors&lt;&#x2F;strong&gt; at the beginning, audio tracks that aren’t a filesystem at all, and per-sector headers that ISO expects stripped.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;bchunk&lt;&#x2F;code&gt; does the conversion properly:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Data tracks&lt;&#x2F;strong&gt; become ISO 9660 files (&lt;code&gt;.iso&lt;&#x2F;code&gt;) — the 2352-byte CD sectors are trimmed to 2048-byte ISO sectors, headers discarded.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Audio tracks&lt;&#x2F;strong&gt; become 16-bit stereo 44.1 kHz &lt;code&gt;.wav&lt;&#x2F;code&gt; files, each with a proper WAV header.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;After that you can &lt;code&gt;mount -o loop&lt;&#x2F;code&gt; the &lt;code&gt;.iso&lt;&#x2F;code&gt;, play the &lt;code&gt;.wav&lt;&#x2F;code&gt;s, or feed them to a burner.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;installing&quot;&gt;Installing&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;debian-ubuntu-mint&quot;&gt;Debian &#x2F; Ubuntu &#x2F; Mint&lt;&#x2F;h3&gt;
&lt;pre&gt;&lt;code&gt;sudo apt install bchunk
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;macos-homebrew&quot;&gt;macOS (Homebrew)&lt;&#x2F;h3&gt;
&lt;pre&gt;&lt;code&gt;brew install bchunk
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;arch-manjaro&quot;&gt;Arch &#x2F; Manjaro&lt;&#x2F;h3&gt;
&lt;pre&gt;&lt;code&gt;sudo pacman -S bchunk
# or from AUR: yay -S bchunk
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;fedora-rhel-centos&quot;&gt;Fedora &#x2F; RHEL &#x2F; CentOS&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;code&gt;bchunk&lt;&#x2F;code&gt; isn’t in the default repos; get it from RPMFusion or build from source:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;git clone https:&#x2F;&#x2F;github.com&#x2F;hessu&#x2F;bchunk.git
cd bchunk
make
sudo cp bchunk &#x2F;usr&#x2F;local&#x2F;bin&#x2F;
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;windows&quot;&gt;Windows&lt;&#x2F;h3&gt;
&lt;p&gt;Use WSL and &lt;code&gt;apt install bchunk&lt;&#x2F;code&gt;. There’s an old native port but WSL is easier.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;basic-usage&quot;&gt;Basic usage&lt;&#x2F;h2&gt;
&lt;pre&gt;&lt;code&gt;bchunk game.bin game.cue game
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Output:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;game01.iso    # data track, mountable as ISO 9660
game02.wav    # audio track 1
game03.wav    # audio track 2
...
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;basename&lt;&#x2F;code&gt; (&lt;code&gt;game&lt;&#x2F;code&gt; in the example) is the prefix for every output file. Track numbers start at &lt;code&gt;01&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;useful-options&quot;&gt;Useful options&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;code&gt;bchunk&lt;&#x2F;code&gt; has a small, stable set of flags — most of them are there to handle specific CD quirks.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-v&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — verbose. Prints each track as it’s processed. Use this the first time you run it to confirm you’re getting the track layout you expect.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-w&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — write &lt;code&gt;.wav&lt;&#x2F;code&gt; files (default). Explicit form.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-r&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — raw audio output instead of &lt;code&gt;.wav&lt;&#x2F;code&gt;. Useful if you’re piping into &lt;code&gt;sox&lt;&#x2F;code&gt; or &lt;code&gt;ffmpeg&lt;&#x2F;code&gt; and don’t want a second header-stripping step.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-p&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — &lt;strong&gt;Psx&#x2F;PlayStation mode&lt;&#x2F;strong&gt;. Treats Mode 2 sectors more loosely — needed for many PlayStation CD images where the cue sheet is imprecise.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-s&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — swap byte order on audio tracks. Needed if the &lt;code&gt;.bin&lt;&#x2F;code&gt; was produced by a ripper that wrote little-endian audio where &lt;code&gt;bchunk&lt;&#x2F;code&gt; expects big-endian (or vice versa). Symptom: output &lt;code&gt;.wav&lt;&#x2F;code&gt; sounds like white noise.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-W&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — write &lt;code&gt;.wav&lt;&#x2F;code&gt; with a verified (rather than computed) header size. Rarely needed.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-E&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — use the exact track boundaries from the cue sheet rather than probing. Use this if audio tracks sound clipped at the beginning or end.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;mounting-the-resulting-iso&quot;&gt;Mounting the resulting .iso&lt;&#x2F;h2&gt;
&lt;p&gt;On Linux:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;sudo mkdir &#x2F;mnt&#x2F;cd
sudo mount -o loop game01.iso &#x2F;mnt&#x2F;cd
ls &#x2F;mnt&#x2F;cd
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;On macOS:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;hdiutil attach game01.iso
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;common-gotchas&quot;&gt;Common gotchas&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;illegal-or-unsupported-track-type-or-empty-output&quot;&gt;“Illegal or unsupported track type” or empty output&lt;&#x2F;h3&gt;
&lt;p&gt;Your cue sheet is malformed or references a &lt;code&gt;.bin&lt;&#x2F;code&gt; size that doesn’t match. Open the &lt;code&gt;.cue&lt;&#x2F;code&gt; in a text editor — it’s plain text — and check:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;FILE &quot;...&quot;&lt;&#x2F;code&gt; line points at the right &lt;code&gt;.bin&lt;&#x2F;code&gt; filename, case-sensitive.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;TRACK&lt;&#x2F;code&gt; entries are numbered sequentially with types &lt;code&gt;MODE1&#x2F;2352&lt;&#x2F;code&gt;, &lt;code&gt;MODE2&#x2F;2352&lt;&#x2F;code&gt;, or &lt;code&gt;AUDIO&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;Index entries (&lt;code&gt;INDEX 00&lt;&#x2F;code&gt;, &lt;code&gt;INDEX 01&lt;&#x2F;code&gt;) are MM:SS:FF, with FF being frames (0–74).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;audio-tracks-sound-like-static&quot;&gt;Audio tracks sound like static&lt;&#x2F;h3&gt;
&lt;p&gt;Byte order mismatch. Retry with &lt;code&gt;-s&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;bchunk -s game.bin game.cue game
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;single-iso-is-fine-but-audio-tracks-are-silent-or-truncated&quot;&gt;Single &lt;code&gt;.iso&lt;&#x2F;code&gt; is fine, but audio tracks are silent or truncated&lt;&#x2F;h3&gt;
&lt;p&gt;Try &lt;code&gt;-E&lt;&#x2F;code&gt; to force cue-sheet-exact boundaries:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;bchunk -E game.bin game.cue game
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;playstation-psx-image-won-t-split-cleanly&quot;&gt;PlayStation&#x2F;PSX image won’t split cleanly&lt;&#x2F;h3&gt;
&lt;p&gt;Use &lt;code&gt;-p&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;bchunk -p psx.bin psx.cue psx
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is the most common complaint on forums — PSX cues are almost always Mode 2 and &lt;code&gt;bchunk&lt;&#x2F;code&gt; without &lt;code&gt;-p&lt;&#x2F;code&gt; is strict about them.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;multiple-bin-files-referenced-in-the-cue&quot;&gt;Multiple &lt;code&gt;.bin&lt;&#x2F;code&gt; files referenced in the cue&lt;&#x2F;h3&gt;
&lt;p&gt;Some rippers produce one &lt;code&gt;.bin&lt;&#x2F;code&gt; per track plus a combining cue sheet. &lt;code&gt;bchunk&lt;&#x2F;code&gt; expects one &lt;code&gt;.bin&lt;&#x2F;code&gt; for the whole disc. Concatenate them first with &lt;code&gt;cat track01.bin track02.bin ... &amp;gt; combined.bin&lt;&#x2F;code&gt; and point a single-file cue at &lt;code&gt;combined.bin&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;alternatives-worth-knowing&quot;&gt;Alternatives worth knowing&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;cdemu&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — user-space CD emulator for Linux. Can mount a &lt;code&gt;.bin&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;.cue&lt;&#x2F;code&gt; directly without splitting. Useful if you just want to &lt;em&gt;read&lt;&#x2F;em&gt; the data track temporarily.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;ecm-tools&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — different format (&lt;code&gt;.ecm&lt;&#x2F;code&gt;), but same problem space. Worth knowing if you encounter it.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;7z&lt;&#x2F;code&gt; &#x2F; &lt;code&gt;p7zip&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — newer 7-Zip versions can list and extract ISO 9660 data tracks from a raw &lt;code&gt;.bin&lt;&#x2F;code&gt;, but can’t recover audio as &lt;code&gt;.wav&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;ffmpeg&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — if you want MP3 &#x2F; FLAC &#x2F; Opus instead of &lt;code&gt;.wav&lt;&#x2F;code&gt;, pipe the raw output: &lt;code&gt;bchunk -r game.bin game.cue game &amp;amp;&amp;amp; ffmpeg -f s16be -ar 44100 -ac 2 -i game02.raw game02.flac&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;why-it-s-still-useful-in-2026&quot;&gt;Why it’s still useful in 2026&lt;&#x2F;h2&gt;
&lt;p&gt;Optical-media imaging has been a solved problem for decades, but &lt;code&gt;.bin&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;.cue&lt;&#x2F;code&gt; is still the default output of &lt;code&gt;cdrdao&lt;&#x2F;code&gt;, still what old archives are stored as, and still what shows up when someone hands you a CD backup that isn’t an ISO. &lt;code&gt;bchunk&lt;&#x2F;code&gt; is a ~1,000-line C program, written in the late 1990s, that does one job correctly and has barely changed in twenty years. It’s in every major distribution, it compiles cleanly on modern systems, and it’s the right tool.&lt;&#x2F;p&gt;
&lt;p&gt;If you’re trying to recover data from an old CD image: start here.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>binlist.net — how a Fuerteventura holiday side-project became iinlist.com</title>
        <published>2026-04-22T00:00:00+00:00</published>
        <updated>2026-04-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.x2q.net/post/binlist-net-iinlist-com-story/"/>
        <id>https://www.x2q.net/post/binlist-net-iinlist-com-story/</id>
        
        <content type="html" xml:base="https://www.x2q.net/post/binlist-net-iinlist-com-story/">&lt;p&gt;&lt;strong&gt;TL;DR —&lt;&#x2F;strong&gt; &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;binlist.net&quot;&gt;binlist.net&lt;&#x2F;a&gt; is a &lt;strong&gt;free HTTP API&lt;&#x2F;strong&gt; that takes the first 6–8 digits of a credit card number (the &lt;strong&gt;BIN&lt;&#x2F;strong&gt; or &lt;strong&gt;IIN&lt;&#x2F;strong&gt;) and returns the issuing bank, country, card scheme (Visa, Mastercard, etc.), card type (debit&#x2F;credit&#x2F;prepaid), and category. I started it in &lt;strong&gt;August 2013 on a holiday in Fuerteventura&lt;&#x2F;strong&gt;, because I needed a BIN lookup for another project and everything that existed was either paywalled, stale, or behind a sketchy scraper. It was my &lt;strong&gt;first ever Go project&lt;&#x2F;strong&gt;, hit &lt;strong&gt;100,000 queries&#x2F;day&lt;&#x2F;strong&gt; within weeks, and crossed &lt;strong&gt;1 million queries&#x2F;day in 2015&lt;&#x2F;strong&gt;. It is still online, still free, still &lt;code&gt;curl&lt;&#x2F;code&gt;-friendly. The commercial variant, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;iinlist.com&quot;&gt;iinlist.com&lt;&#x2F;a&gt;, is what I co-founded after binlist.net’s success; it’s the same idea with &lt;strong&gt;payment-industry-grade accuracy&lt;&#x2F;strong&gt;, &lt;strong&gt;8-digit IIN support&lt;&#x2F;strong&gt; (mandated by ISO&#x2F;IEC 7812 since 2022), &lt;strong&gt;ARDEF-backed ranges&lt;&#x2F;strong&gt;, and an SLA — built for banks, PSPs, fraud teams, and issuers who need to treat BIN data as production data.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;bin-vs-iin-a-one-paragraph-primer&quot;&gt;BIN vs IIN — a one-paragraph primer&lt;&#x2F;h2&gt;
&lt;p&gt;The first six (historically) or eight (since 2017, mandatory 2022) digits of a card number are the &lt;strong&gt;Issuer Identification Number&lt;&#x2F;strong&gt; (IIN). The old name, still widely used, is &lt;strong&gt;Bank Identification Number&lt;&#x2F;strong&gt; (BIN). Given a BIN&#x2F;IIN, you can determine:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Scheme &#x2F; network&lt;&#x2F;strong&gt; — Visa, Mastercard, Amex, Discover, JCB, UnionPay, Diners.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Issuer&lt;&#x2F;strong&gt; — the bank or fintech that issued the card.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Country&lt;&#x2F;strong&gt; — country of the issuer, which is not necessarily the cardholder’s country but is a useful proxy for risk scoring, tax logic (EU&#x2F;EEA VAT MOSS), and user experience (preferred language, local payment methods).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Type&lt;&#x2F;strong&gt; — debit, credit, prepaid, deferred debit, charge.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Category &#x2F; product&lt;&#x2F;strong&gt; — Classic, Gold, Platinum, Business, Corporate, etc. Useful for acceptance cost routing (commercial cards cost more to accept).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;All of this is “sensitive” in the sense that issuers don’t publish full BIN tables and card schemes consider them proprietary. In practice, BIN ranges leak constantly — every authorisation the scheme routes, every chargeback dispute — and there’s a whole cottage industry around keeping reasonably accurate BIN tables.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-i-built-binlist-net-on-holiday&quot;&gt;Why I built binlist.net on holiday&lt;&#x2F;h2&gt;
&lt;p&gt;Summer 2013. &lt;strong&gt;Playitas, southern Fuerteventura&lt;&#x2F;strong&gt; — the resort on the Atlantic side down by Gran Tarajal. Rented apartment, small balcony, Canary wind doing its thing. I had an idea for a side-project that needed to look up cards by BIN and I did not want to pay a payments vendor €200&#x2F;month for it. The existing free options were:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Wikipedia’s IIN list&lt;&#x2F;strong&gt; — reasonably accurate for well-known schemes, woefully incomplete for niche issuers, updated by volunteers who don’t work in payments.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Pasted CSVs on forums&lt;&#x2F;strong&gt; — decent coverage for US issuers, always outdated.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Google Fusion Tables&lt;&#x2F;strong&gt; — deprecated by Google that same year.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Scrapers of merchant-bank lookup forms&lt;&#x2F;strong&gt; — ToS-violating, brittle.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;What I wanted was &lt;code&gt;curl http:&#x2F;&#x2F;example.com&#x2F;40012345&lt;&#x2F;code&gt; returning clean JSON. So I built it.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-stack-then&quot;&gt;The stack (then)&lt;&#x2F;h3&gt;
&lt;p&gt;This was &lt;strong&gt;my first ever Go project&lt;&#x2F;strong&gt;. I’d been reading about Go for a while and wanted a small, well-scoped excuse to try it; a single-endpoint JSON API over an in-memory BIN table was perfect. Ruby&#x2F;Sinatra would have been faster for me to type, but I wanted to learn something, and the memory and latency characteristics of a Go binary turned out to be exactly what a free-tier public API needed.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Go&lt;&#x2F;strong&gt; — a single binary, &lt;code&gt;net&#x2F;http&lt;&#x2F;code&gt;, no framework. The whole server was a few hundred lines.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;In-memory BIN range table&lt;&#x2F;strong&gt;, built at boot from a ~300 MB source file. Lookups were O(log n) over a sorted slice.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Deployed on a single Hetzner VM&lt;&#x2F;strong&gt; from day one (I briefly considered Heroku but Heroku’s free dyno memory limits didn’t fit a fully-loaded BIN table).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;JSON + XML + CSV&lt;&#x2F;strong&gt; response formats, because 2013.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Data seed&lt;&#x2F;strong&gt; from a combination of public Wikipedia scrapes, the old “Mars Base” CSV from 2009, and a set of ranges I’d been accumulating at work.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Total build time on that balcony: a weekend — about half of which was me figuring out &lt;code&gt;go build&lt;&#x2F;code&gt;, &lt;code&gt;GOPATH&lt;&#x2F;code&gt; (Go modules didn’t exist yet), and how interfaces worked. I bought the domain on the Monday, deployed the binary, and tweeted about it.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;traffic-curve&quot;&gt;Traffic curve&lt;&#x2F;h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Week 1:&lt;&#x2F;strong&gt; a few hundred queries&#x2F;day, mostly me testing.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Month 1:&lt;&#x2F;strong&gt; ~&lt;strong&gt;100,000 queries&#x2F;day&lt;&#x2F;strong&gt; after developers on Stack Overflow and a couple of e-commerce forums found it.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;By early 2015:&lt;&#x2F;strong&gt; crossed &lt;strong&gt;1 million queries&#x2F;day&lt;&#x2F;strong&gt; sustained, with spikes above 2M during flash-sale events on merchants that called it on every checkout page-load.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Scaling to that point cost almost nothing because Go’s single-binary footprint meant a ~€5&#x2F;month VPS handled the whole thing; the bottleneck for a long time was the NIC, not CPU or memory.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;13-years-later&quot;&gt;13 years later&lt;&#x2F;h3&gt;
&lt;p&gt;binlist.net still runs. It has migrated infrastructure a few times — Hetzner VM → Hetzner fleet → a small fleet behind Cloudflare — and the data seed has been continuously updated, but the API surface (and most of the original Go code) is the same. Today it serves &lt;strong&gt;tens of millions of lookups per month&lt;&#x2F;strong&gt;, free, no API key required, with a soft rate limit on anonymous callers to keep it honest.&lt;&#x2F;p&gt;
&lt;p&gt;It has &lt;strong&gt;~2,000 Google impressions&#x2F;month&lt;&#x2F;strong&gt; for queries like “binlist”, “bin list bank identification number”, “list of bank identification numbers” — still picking up the long tail of developers who type the same question I typed into Google in 2013.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-iinlist-com-exists&quot;&gt;Why iinlist.com exists&lt;&#x2F;h2&gt;
&lt;p&gt;binlist.net’s data is good enough for “what country is this card from” decisions — risk scoring, currency presentation, analytics. It is &lt;strong&gt;not&lt;&#x2F;strong&gt; good enough for:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Acceptance-cost routing&lt;&#x2F;strong&gt; where you route commercial-card transactions through a different processor.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Interchange++ modelling&lt;&#x2F;strong&gt; where the margin difference between Classic and Platinum Mastercard is real money.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Scheme compliance&lt;&#x2F;strong&gt; — the ISO&#x2F;IEC 7812 migration from 6-digit BINs to 8-digit IINs, mandated 2022, broke a lot of “I’ll just use the first six digits” logic.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Co-badged cards&lt;&#x2F;strong&gt; (e.g. Dankort + Visa Debit) where two networks both claim the card and the routing decision is business-critical.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;ARDEF-grade accuracy&lt;&#x2F;strong&gt; — Visa’s Account Range Definition File is the source of truth; you really do want data derived from ARDEF for anything production-grade.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;iinlist.com solves these, commercially. I co-founded it with a small team of people who know payments data professionally. It’s the product binlist.net is the free-tier advertisement for. Some of our customers are names you’d recognise; most are not. All of them eventually reach the same conclusion: &lt;strong&gt;BIN data that’s “mostly right” costs more in wrong decisions than accurate data costs in licence fees&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-iinlist-com-is-specifically&quot;&gt;What iinlist.com is, specifically&lt;&#x2F;h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;8-digit IIN support&lt;&#x2F;strong&gt; across all schemes.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Daily updates&lt;&#x2F;strong&gt;, derived from ARDEF + scheme feeds + issuer announcements + internal verification.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Richer enrichment&lt;&#x2F;strong&gt;: card type, product category, issuing country, issuer branding, regulatory classifications (commercial vs consumer, prepaid flag, etc.).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;SLA and support&lt;&#x2F;strong&gt;. You can file a ticket and it will actually reach a human.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;On-prem &#x2F; self-hosted option&lt;&#x2F;strong&gt; for customers whose compliance doesn’t allow “BIN lookup as a third-party API call in the authorisation path”.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Pricing&lt;&#x2F;strong&gt; that makes sense for both a scrappy fintech (monthly plan) and an established acquirer (annual, unlimited).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;can-i-just-use-binlist-net-commercially&quot;&gt;Can I just use binlist.net commercially?&lt;&#x2F;h2&gt;
&lt;p&gt;You can use binlist.net however you want; there’s no gate. But:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;There’s no SLA, no uptime guarantee, and no contract. It’s a side project that happens to have stayed up for 13 years.&lt;&#x2F;li&gt;
&lt;li&gt;Rate limits on anonymous traffic will kick in at production volume. Buy a licence from iinlist.com and the problem is solved.&lt;&#x2F;li&gt;
&lt;li&gt;If your regulator asks where your BIN data comes from, “a free API my developer found” is not the answer you want to give.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;For anything hobby, demo, or prototype: binlist.net is perfect. For anything where wrong BIN data becomes someone’s KPI: iinlist.com.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;using-binlist-net-today&quot;&gt;Using binlist.net today&lt;&#x2F;h2&gt;
&lt;pre&gt;&lt;code&gt;$ curl -sH &amp;quot;Accept-Version: 3&amp;quot; https:&#x2F;&#x2F;lookup.binlist.net&#x2F;45717360
{
  &amp;quot;number&amp;quot;: { &amp;quot;length&amp;quot;: 16, &amp;quot;luhn&amp;quot;: true },
  &amp;quot;scheme&amp;quot;: &amp;quot;visa&amp;quot;,
  &amp;quot;type&amp;quot;: &amp;quot;debit&amp;quot;,
  &amp;quot;brand&amp;quot;: &amp;quot;Visa&#x2F;Dankort&amp;quot;,
  &amp;quot;country&amp;quot;: {
    &amp;quot;numeric&amp;quot;: &amp;quot;208&amp;quot;,
    &amp;quot;alpha2&amp;quot;: &amp;quot;DK&amp;quot;,
    &amp;quot;name&amp;quot;: &amp;quot;Denmark&amp;quot;,
    &amp;quot;emoji&amp;quot;: &amp;quot;🇩🇰&amp;quot;,
    &amp;quot;currency&amp;quot;: &amp;quot;DKK&amp;quot;,
    &amp;quot;latitude&amp;quot;: 56,
    &amp;quot;longitude&amp;quot;: 10
  },
  &amp;quot;bank&amp;quot;: {
    &amp;quot;name&amp;quot;: &amp;quot;Jyske Bank A&#x2F;S&amp;quot;,
    &amp;quot;url&amp;quot;: &amp;quot;www.jyskebank.dk&amp;quot;,
    &amp;quot;phone&amp;quot;: &amp;quot;+4589893300&amp;quot;,
    &amp;quot;city&amp;quot;: &amp;quot;Silkeborg&amp;quot;
  }
}
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Accept-Version: 3&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — pins to API v3.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Anonymous rate limit&lt;&#x2F;strong&gt; — documented on the site, plenty for casual use.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Zero warranties&lt;&#x2F;strong&gt; — if the response is wrong, submit a correction on the GitHub repo. For commercial guarantees, see iinlist.com.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;faq&quot;&gt;FAQ&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;is-bin-iin-data-regulated&quot;&gt;Is BIN &#x2F; IIN data regulated?&lt;&#x2F;h3&gt;
&lt;p&gt;BIN ranges themselves are not personal data under GDPR — a BIN identifies an issuer, not a person. Combining a BIN with a full card number is a different story and falls under PCI-DSS.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-changed-with-the-8-digit-iin-migration&quot;&gt;What changed with the 8-digit IIN migration?&lt;&#x2F;h3&gt;
&lt;p&gt;ISO&#x2F;IEC 7812 formalised 8-digit IINs in 2017 with a hard migration deadline in April 2022 for the major networks. If your code still treats only the first six digits as the issuer identifier, you’re silently misrouting a growing share of traffic — modern Visa and Mastercard issuer ranges are defined at 8 digits.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;why-not-just-look-it-up-client-side&quot;&gt;Why not just look it up client-side?&lt;&#x2F;h3&gt;
&lt;p&gt;You can, with a static list in the browser. But you’ll accept a trade-off: freshness (the static list will be out of date within weeks) or bundle size (the current BIN table is megabytes uncompressed). A server-side API is the conventional answer.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;does-binlist-net-log-the-bins-i-query&quot;&gt;Does binlist.net log the BINs I query?&lt;&#x2F;h3&gt;
&lt;p&gt;Aggregate analytics only — enough to detect abuse and rate-limit. Individual queries are not associated with a user. iinlist.com has a stricter no-logging posture for customers where that matters.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;is-the-source-code-open&quot;&gt;Is the source code open?&lt;&#x2F;h3&gt;
&lt;p&gt;The API surface of binlist.net is on GitHub, with contribution guidelines for data corrections. The data ingestion pipeline is not open-source; it relies on scheme feeds that require licences.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;are-binlist-net-and-iinlist-com-the-same-company&quot;&gt;Are binlist.net and iinlist.com the same company?&lt;&#x2F;h3&gt;
&lt;p&gt;Separate projects, overlapping origins, same operator on my side. binlist.net is a personal free service; iinlist.com is a commercial company with co-founders and employees. The two are operationally independent but share institutional knowledge about BIN data that is not easy to accumulate from scratch.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;thirteen-years-in&quot;&gt;Thirteen years in&lt;&#x2F;h2&gt;
&lt;p&gt;The thing I find most interesting about binlist.net is not the traffic — it’s the &lt;strong&gt;kind of questions&lt;&#x2F;strong&gt; developers are still typing into Google in 2026: “what is a bin list”, “bin number example”, “list of issuer identification numbers”. These are the exact queries I was typing in 2013 on that balcony in Playitas. Payments as an industry keeps adding layers, but the foundational questions don’t change.&lt;&#x2F;p&gt;
&lt;p&gt;If you’re sitting on a beach with a laptop and you have an itch for a side-project that scratches your own back, go build it — and while you’re at it, try the language you’ve been meaning to learn. Sometimes it turns out a lot of people have the same itch, and thirteen years later you have a useful thing to point them at and a working knowledge of Go.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>My CM3588 project — a 4-NVMe ARM64 home NAS on 10 W idle</title>
        <published>2026-04-22T00:00:00+00:00</published>
        <updated>2026-04-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.x2q.net/post/cm3588-home-nas-project/"/>
        <id>https://www.x2q.net/post/cm3588-home-nas-project/</id>
        
        <content type="html" xml:base="https://www.x2q.net/post/cm3588-home-nas-project/">&lt;p&gt;&lt;strong&gt;TL;DR —&lt;&#x2F;strong&gt; The &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;wiki.friendlyelec.com&#x2F;wiki&#x2F;index.php&#x2F;CM3588_NAS_Kit&quot;&gt;FriendlyElec CM3588 NAS Kit&lt;&#x2F;a&gt; is an RK3588-based compute module on a carrier board with &lt;strong&gt;four M.2 NVMe slots (PCIe 3.0 x1 each)&lt;&#x2F;strong&gt;, &lt;strong&gt;2.5 GbE&lt;&#x2F;strong&gt;, &lt;strong&gt;2× HDMI out&lt;&#x2F;strong&gt;, &lt;strong&gt;USB 3.0&lt;&#x2F;strong&gt;, and an 8- or 16-core ARMv8 CPU (4× Cortex-A76 + 4× Cortex-A55). Fully loaded with four NVMe drives it idles at about &lt;strong&gt;10 W&lt;&#x2F;strong&gt;, peaks around 20 W under heavy I&#x2F;O, and reads&#x2F;writes an NVMe at roughly 1 GB&#x2F;s (the bus limit, not the drive). My own unit runs Debian on the mainline kernel, with ZFS on root, four 2 TB NVMe drives in a &lt;code&gt;raidz2&lt;&#x2F;code&gt; pool, Samba for LAN shares, and Tailscale for off-LAN access. It has replaced a tower PC running TrueNAS and cut NAS power draw by about 60 W.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-this-board&quot;&gt;Why this board&lt;&#x2F;h2&gt;
&lt;p&gt;I’d been running a tower PC as a home NAS for years: a second-hand desktop, six 3.5“ spinning disks, and a noisy fan. It worked, but it was using 70–80 W idle and sitting in a cupboard that needed active ventilation. The disks were loud enough that I’d moved them to the far corner of the house.&lt;&#x2F;p&gt;
&lt;p&gt;Three things I wanted from a replacement:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;NVMe-only.&lt;&#x2F;strong&gt; Spinning disks are cheaper per terabyte but the noise, vibration, and heat aren’t worth it for a home NAS sized in the single-digit terabytes. 4× 2 TB NVMe is plenty.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;ARM, for the power draw.&lt;&#x2F;strong&gt; An x86 board with 4× PCIe slots wired to NVMe would pull 30–40 W idle. ARM can do it in 10.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Mainline Linux, not a vendor fork.&lt;&#x2F;strong&gt; I want &lt;code&gt;apt-get dist-upgrade&lt;&#x2F;code&gt; to work, &lt;code&gt;zfs&lt;&#x2F;code&gt; to compile from DKMS against a current kernel, and no three-year-old vendor BSP.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;The CM3588 NAS Kit hits all three.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;hardware&quot;&gt;Hardware&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SoC:&lt;&#x2F;strong&gt; Rockchip RK3588 (4× Cortex-A76 @ 2.4 GHz + 4× Cortex-A55 @ 1.8 GHz, Mali-G610 GPU, 6 TOPS NPU). ARMv8.2-A.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;RAM:&lt;&#x2F;strong&gt; 8 GB LPDDR4X on the module I bought. 16 GB and 32 GB variants exist.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Storage:&lt;&#x2F;strong&gt; 64 GB eMMC on-module for OS + 4× M.2 Key-M 2280 slots, each &lt;strong&gt;PCIe Gen 3 x1&lt;&#x2F;strong&gt; (so ~1 GB&#x2F;s per slot, not x4).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Network:&lt;&#x2F;strong&gt; 1× 2.5 GbE (Realtek RTL8125B).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;USB:&lt;&#x2F;strong&gt; 2× USB 3.0 Type-A, 1× USB 3.0 Type-C (DisplayPort alt-mode), 1× USB 2.0.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Video:&lt;&#x2F;strong&gt; 2× HDMI out, 1× HDMI &lt;strong&gt;in&lt;&#x2F;strong&gt; (RK3588’s capture block).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Power:&lt;&#x2F;strong&gt; 12 V barrel jack. A 60 W brick is more than enough.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The board is small — roughly 10 × 10 cm — and passive over the SoC plus a small fan blowing across the NVMe drives. Mine sits flat on a shelf without a case.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;nvme-compatibility-caveat&quot;&gt;NVMe compatibility caveat&lt;&#x2F;h3&gt;
&lt;p&gt;The one gotcha worth knowing: the CM3588 NAS Kit is &lt;strong&gt;not compatible with some WD Blue SN580 and WD Black SN850 drives&lt;&#x2F;strong&gt; — the linkup flaps at boot. I use Samsung 990 EVO and Crucial P3 Plus; both are fine. Check the FriendlyElec wiki compatibility list before buying NVMe drives.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;os-and-filesystem&quot;&gt;OS and filesystem&lt;&#x2F;h2&gt;
&lt;p&gt;I run &lt;strong&gt;Debian 13 (trixie)&lt;&#x2F;strong&gt; with the &lt;strong&gt;mainline kernel&lt;&#x2F;strong&gt; — RK3588 support is good enough from 6.8 onwards for headless use. The FriendlyElec-provided Debian image works out of the box, but I reinstall on top of it to get a clean Debian rather than their BSP.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Root filesystem:&lt;&#x2F;strong&gt; &lt;code&gt;ext4&lt;&#x2F;code&gt; on eMMC.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Data pool:&lt;&#x2F;strong&gt; &lt;code&gt;zfs&lt;&#x2F;code&gt; &lt;code&gt;raidz2&lt;&#x2F;code&gt; across the four NVMe drives. With 4× 2 TB, &lt;code&gt;raidz2&lt;&#x2F;code&gt; gives ~3.5 TB usable with two-disk redundancy. Yes, &lt;code&gt;raidz2&lt;&#x2F;code&gt; on four drives is conservative; it also means a double failure doesn’t eat the pool, and on NVMe the rebuild is fast enough that I don’t lose sleep.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;ZFS module:&lt;&#x2F;strong&gt; DKMS against the running kernel. Rebuilds automatically on kernel upgrades. Has worked without manual intervention across three kernel bumps.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;ARC tuning:&lt;&#x2F;strong&gt; Capped at 2 GB. This leaves headroom on an 8 GB system.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Throughput is bottlenecked by 2.5 GbE for network I&#x2F;O (~280 MB&#x2F;s sustained) long before ZFS or the NVMe drives break a sweat. Local operations on the pool hit around 2 GB&#x2F;s aggregate sequential read.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;services&quot;&gt;Services&lt;&#x2F;h2&gt;
&lt;p&gt;Kept deliberately simple:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Samba&lt;&#x2F;strong&gt; for SMB shares to the Macs and Windows box on the LAN.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;rsync + cron&lt;&#x2F;strong&gt; for nightly backups from three other machines.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;ZFS snapshots&lt;&#x2F;strong&gt; every hour, retained for a week. &lt;code&gt;zfs send&lt;&#x2F;code&gt; pipes the dataset weekly to an off-site B2 bucket via &lt;code&gt;zfs-autobackup&lt;&#x2F;code&gt; + rclone.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Tailscale&lt;&#x2F;strong&gt; for off-LAN access. No port-forwarding, no dyndns, no VPN concentrator. SSH and Samba both ride the Tailscale interface when I’m away.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Cockpit&lt;&#x2F;strong&gt; for the occasional “why is the fan loud” dashboard moment.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;That’s it. No Docker swarm, no Kubernetes, no dozen sidecar containers. The board does one job — serve files on the LAN and let me pull backups off-site — and it does it quietly.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;power-and-noise&quot;&gt;Power and noise&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Idle&lt;&#x2F;strong&gt; (all four NVMe spun up, no traffic): &lt;strong&gt;~10 W&lt;&#x2F;strong&gt; at the wall.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Busy&lt;&#x2F;strong&gt; (NVMe read saturating 2.5 GbE, ZFS ARC warm): &lt;strong&gt;~18–20 W&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Peak&lt;&#x2F;strong&gt; (scrub across the whole pool, all cores hot): &lt;strong&gt;~25 W&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Compared to the 70–80 W tower it replaced, the ROI on electricity alone is about &lt;strong&gt;500 kWh&#x2F;year&lt;&#x2F;strong&gt;, or roughly &lt;strong&gt;1,500 kr&#x2F;year&lt;&#x2F;strong&gt; at Danish residential rates. The board paid for itself in about a year.&lt;&#x2F;p&gt;
&lt;p&gt;Noise: effectively inaudible from more than a metre away. The fan is small and 80% of the time it’s barely spinning.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-didn-t-work-or-took-effort&quot;&gt;What didn’t work, or took effort&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Vendor kernel.&lt;&#x2F;strong&gt; The FriendlyElec-shipped kernel is FriendlyElec’s fork of 6.1 with RK-specific patches. It works for their turnkey NAS image, but DKMS ZFS builds are painful against it. Moving to mainline fixed this.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;HDMI capture.&lt;&#x2F;strong&gt; The RK3588 has a hardware HDMI input block. In mainline it’s still experimental. I have a rough use case for it (pulling a signal from an old device for archival) but haven’t made it work end-to-end. Parked for now.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;NPU.&lt;&#x2F;strong&gt; The 6 TOPS NPU is underused. Mainline has basic RKNPU support but for anything practical you still need FriendlyElec’s &lt;code&gt;rknn-toolkit2&lt;&#x2F;code&gt;. Not a blocker for a NAS.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;faq&quot;&gt;FAQ&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;why-not-x86&quot;&gt;Why not x86?&lt;&#x2F;h3&gt;
&lt;p&gt;An x86 mini-PC with 4× NVMe is ~2× the idle power and ~2× the purchase price (once you include the case, PSU, and CPU). For a home NAS where the workload is “serve files” rather than “run 40 containers”, ARM wins.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;why-not-raspberry-pi-5&quot;&gt;Why not Raspberry Pi 5?&lt;&#x2F;h3&gt;
&lt;p&gt;The Pi 5 has one PCIe 2.0 x1 lane exposed via an HAT — you can bolt on an NVMe, but only one, and at ~500 MB&#x2F;s ceiling. The CM3588 has four lanes at PCIe 3.0 wired directly. Different class of board.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;why-not-openmediavault-or-truenas&quot;&gt;Why not OpenMediaVault or TrueNAS?&lt;&#x2F;h3&gt;
&lt;p&gt;Both would work. I prefer plain Debian + Samba + ZFS because the upgrade path for Debian is well-known to me, and OMV&#x2F;TrueNAS are a layer I’d have to re-learn. Your mileage will vary.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;why-raidz2-on-four-drives-and-not-mirror-mirror&quot;&gt;Why &lt;code&gt;raidz2&lt;&#x2F;code&gt; on four drives and not &lt;code&gt;mirror + mirror&lt;&#x2F;code&gt;?&lt;&#x2F;h3&gt;
&lt;p&gt;I considered it. &lt;code&gt;mirror + mirror&lt;&#x2F;code&gt; gives you faster writes and easier expansion but only 4 TB usable from 4× 2 TB, same as &lt;code&gt;raidz2&lt;&#x2F;code&gt;. With &lt;code&gt;raidz2&lt;&#x2F;code&gt; any two drives can fail; with striped mirrors you can lose two drives, but only if they’re in different mirror pairs. On four drives the capacity is identical and &lt;code&gt;raidz2&lt;&#x2F;code&gt; has the stronger failure tolerance.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;can-it-run-plex-jellyfin&quot;&gt;Can it run Plex &#x2F; Jellyfin?&lt;&#x2F;h3&gt;
&lt;p&gt;Yes, and the RK3588’s hardware H.265&#x2F;VP9&#x2F;AV1 decode block does the heavy lifting. Jellyfin with &lt;code&gt;rkmpp&lt;&#x2F;code&gt; accelerated decoding handles 4K HEVC transcodes at about 3× realtime. I don’t run either on this box — my Plex lives elsewhere — but it’s viable.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;does-it-suspend&quot;&gt;Does it suspend?&lt;&#x2F;h3&gt;
&lt;p&gt;Not reliably. Leave it on; idle is 10 W anyway.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-about-noise-and-heat-in-summer&quot;&gt;What about noise and heat in summer?&lt;&#x2F;h3&gt;
&lt;p&gt;Passive heatsink plus one small fan. In a Danish summer (20–25 °C ambient) it tops out around 55 °C die temp under a ZFS scrub. No throttling I’ve measured.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;verdict&quot;&gt;Verdict&lt;&#x2F;h2&gt;
&lt;p&gt;If you want a &lt;strong&gt;quiet, low-power, all-NVMe home NAS with enough PCIe to matter&lt;&#x2F;strong&gt; and you’re willing to spend an evening on Debian + ZFS, the CM3588 NAS Kit is the best option I’ve found. It’s not the cheapest — a second-hand Optiplex is cheaper upfront — but on three-year TCO including electricity it pays back clearly.&lt;&#x2F;p&gt;
&lt;p&gt;The RK3588 itself is a surprisingly capable SoC for homelab workloads. Once mainline kernel support landed, I stopped treating it as “an ARM curiosity” and started treating it as a default answer for small always-on servers.&lt;&#x2F;p&gt;
&lt;p&gt;Two years in, mine has been rebooted twice (once for a kernel upgrade, once because I moved the shelf). I’d buy it again.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>fcrackzip — recover lost zip passwords on Linux, macOS, and Windows (2026)</title>
        <published>2026-04-22T00:00:00+00:00</published>
        <updated>2026-04-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.x2q.net/post/crack-password-encrypted-zip/"/>
        <id>https://www.x2q.net/post/crack-password-encrypted-zip/</id>
        
        <content type="html" xml:base="https://www.x2q.net/post/crack-password-encrypted-zip/">&lt;p&gt;&lt;strong&gt;TL;DR —&lt;&#x2F;strong&gt; &lt;strong&gt;&lt;code&gt;fcrackzip&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; is a free, open-source command-line tool for recovering the password of an encrypted &lt;code&gt;.zip&lt;&#x2F;code&gt; file. It supports &lt;strong&gt;brute-force&lt;&#x2F;strong&gt; (over a user-defined charset and length) and &lt;strong&gt;dictionary&lt;&#x2F;strong&gt; attacks, works on Linux&#x2F;macOS&#x2F;WSL, and is the right first tool when you need to recover a forgotten password on a zip you legitimately own. It is only fast against &lt;strong&gt;classic ZipCrypto&lt;&#x2F;strong&gt; encryption; for the newer &lt;strong&gt;AES-256&lt;&#x2F;strong&gt; zip profile, &lt;code&gt;fcrackzip&lt;&#x2F;code&gt; will not work — you want &lt;code&gt;zip2john&lt;&#x2F;code&gt; + &lt;code&gt;hashcat&lt;&#x2F;code&gt; instead. Install with &lt;code&gt;apt install fcrackzip&lt;&#x2F;code&gt; (Debian&#x2F;Ubuntu), &lt;code&gt;brew install fcrackzip&lt;&#x2F;code&gt; (macOS), or build from source.&lt;&#x2F;p&gt;
&lt;p&gt;This post walks through the practical cases. It assumes you have &lt;strong&gt;authorisation to recover the password&lt;&#x2F;strong&gt; — your own files, a client’s, a CTF. Don’t use it against zips you don’t own.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;install&quot;&gt;Install&lt;&#x2F;h2&gt;
&lt;pre&gt;&lt;code&gt;# Debian &#x2F; Ubuntu &#x2F; Mint &#x2F; Kali
sudo apt install fcrackzip

# macOS (Homebrew)
brew install fcrackzip

# Arch &#x2F; Manjaro
sudo pacman -S fcrackzip

# Windows
# Use WSL2 + Ubuntu and `apt install fcrackzip`.
# A native .exe exists but is old — WSL is simpler.

# From source
git clone https:&#x2F;&#x2F;github.com&#x2F;hyc&#x2F;fcrackzip.git
cd fcrackzip &amp;amp;&amp;amp; .&#x2F;configure &amp;amp;&amp;amp; make
sudo cp fcrackzip &#x2F;usr&#x2F;local&#x2F;bin&#x2F;
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;check-what-kind-of-encryption-your-zip-uses-first&quot;&gt;Check what kind of encryption your zip uses first&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;code&gt;fcrackzip&lt;&#x2F;code&gt; only handles &lt;strong&gt;classic ZipCrypto&lt;&#x2F;strong&gt; (the original, weak PKZIP encryption). For modern &lt;strong&gt;AES-128 &#x2F; AES-256&lt;&#x2F;strong&gt; zips (introduced by WinZip 9 in 2003, now default in some tools), &lt;code&gt;fcrackzip&lt;&#x2F;code&gt; will silently fail to find the password no matter how long you run it.&lt;&#x2F;p&gt;
&lt;p&gt;Quickest check with &lt;code&gt;zipinfo&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;$ zipinfo -v archive.zip | head -40
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Look for &lt;code&gt;WinZip AES encryption, strength 3&lt;&#x2F;code&gt; (= AES-256). If you see that, skip to the hashcat section.&lt;&#x2F;p&gt;
&lt;p&gt;Or with &lt;code&gt;7z&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;$ 7z l -slt archive.zip | grep -i &amp;#39;method\|encrypted&amp;#39;
Method = ZipCrypto Store          ← fcrackzip works
Method = AES-256 Deflate          ← fcrackzip will NOT work
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;basic-brute-force&quot;&gt;Basic brute-force&lt;&#x2F;h2&gt;
&lt;pre&gt;&lt;code&gt;fcrackzip -v -b -c &amp;#39;a1&amp;#39; -p aaaa -u archive.zip
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-v&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — verbose.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-b&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — brute-force mode.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-c &#x27;a1&#x27;&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — charset. &lt;code&gt;a&lt;&#x2F;code&gt; = lowercase &lt;code&gt;a–z&lt;&#x2F;code&gt;, &lt;code&gt;A&lt;&#x2F;code&gt; = uppercase, &lt;code&gt;1&lt;&#x2F;code&gt; = digits, &lt;code&gt;!&lt;&#x2F;code&gt; = printable symbols. Combine: &lt;code&gt;&#x27;aA1!&#x27;&lt;&#x2F;code&gt; = all printable ASCII (94 chars).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-p aaaa&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — starting password (defines minimum length = 4 here).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-u&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — only report if the decrypted file “unzips” cleanly (filters false positives — ZipCrypto has a small CRC that many random passwords pass).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Length: &lt;code&gt;-p aaaaa&lt;&#x2F;code&gt; starts at 5 characters. &lt;code&gt;fcrackzip&lt;&#x2F;code&gt; doesn’t have a &lt;code&gt;--max-length&lt;&#x2F;code&gt; flag; it increments the starting password through the charset forever, so &lt;strong&gt;letting it run to “completion” at length 8 on the full printable ASCII charset is about 94⁸ ≈ 6×10¹⁵ candidates&lt;&#x2F;strong&gt; — not realistic. You bound the attack by picking a reasonable max charset + length, or by using a dictionary.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;dictionary-attack&quot;&gt;Dictionary attack&lt;&#x2F;h2&gt;
&lt;pre&gt;&lt;code&gt;fcrackzip -v -D -u -p &#x2F;usr&#x2F;share&#x2F;wordlists&#x2F;rockyou.txt archive.zip
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-D&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — dictionary mode.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-p &amp;lt;file&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — wordlist file, one candidate per line.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-u&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — verify via unzip, as before.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;code&gt;rockyou.txt&lt;&#x2F;code&gt; is the standard 14 M-entry wordlist shipped with Kali and available everywhere. If you have context about the password (it’s someone’s name + year, it’s a project codename), assemble a smaller targeted wordlist — hit rates are usually much higher than any generic list.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;speed-and-what-to-expect&quot;&gt;Speed and what to expect&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;code&gt;fcrackzip&lt;&#x2F;code&gt; is single-threaded and CPU-bound. On a modern laptop you’ll see roughly:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;10–30 million ZipCrypto attempts per second per core&lt;&#x2F;strong&gt; for a dictionary attack against a typical zip.&lt;&#x2F;li&gt;
&lt;li&gt;Brute-force slightly faster because less string I&#x2F;O.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;That makes it fine for:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Dictionary attacks&lt;&#x2F;strong&gt; against any reasonable wordlist.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Short brute-force&lt;&#x2F;strong&gt; (4–6 chars alphanumeric).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;It makes it not fine for:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Length 8+ with symbols&lt;&#x2F;strong&gt; — you’ll never finish.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;AES-256 zips&lt;&#x2F;strong&gt; — wrong tool.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;when-to-reach-for-hashcat-instead&quot;&gt;When to reach for hashcat instead&lt;&#x2F;h2&gt;
&lt;p&gt;If &lt;code&gt;fcrackzip&lt;&#x2F;code&gt; is too slow, or the zip is AES-encrypted, migrate to &lt;code&gt;hashcat&lt;&#x2F;code&gt;. It’s the general-purpose password-cracking tool, GPU-accelerated, and handles both ZipCrypto and AES WinZip hashes.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;step-1-extract-the-hash-with-zip2john&quot;&gt;Step 1: extract the hash with &lt;code&gt;zip2john&lt;&#x2F;code&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;code&gt;zip2john&lt;&#x2F;code&gt; is part of John the Ripper. Ubuntu: &lt;code&gt;apt install john&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;$ zip2john archive.zip &amp;gt; hash.txt
$ cat hash.txt
archive.zip:$zip2$*0*3*0*...*$&#x2F;zip2$::archive.zip:secret.pdf:&#x2F;path&#x2F;to&#x2F;archive.zip
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;step-2-feed-it-to-hashcat&quot;&gt;Step 2: feed it to hashcat&lt;&#x2F;h3&gt;
&lt;pre&gt;&lt;code&gt;# ZipCrypto (mode 17200, 17210, 17220, 17225 depending on compression)
hashcat -m 17200 hash.txt &#x2F;path&#x2F;to&#x2F;wordlist.txt

# AES-encrypted WinZip (mode 13600)
hashcat -m 13600 hash.txt &#x2F;path&#x2F;to&#x2F;wordlist.txt
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;On a single modern discrete GPU (e.g. RTX 4090) you’ll see:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ZipCrypto: ~5–10 billion H&#x2F;s&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;WinZip AES-256: ~10–50 million H&#x2F;s&lt;&#x2F;strong&gt; (much slower because AES is expensive and PBKDF2 iterations are involved)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;hashcat’s rule engine, mask attacks (&lt;code&gt;-a 3&lt;&#x2F;code&gt;), and combinator mode (&lt;code&gt;-a 1&lt;&#x2F;code&gt;) make it vastly more flexible than &lt;code&gt;fcrackzip&lt;&#x2F;code&gt;. For anything non-trivial it is the right tool.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;common-gotchas&quot;&gt;Common gotchas&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;password-found-pw-abc-but-unzip-still-asks-for-a-password&quot;&gt;“PASSWORD FOUND!!!!: pw == abc” but &lt;code&gt;unzip&lt;&#x2F;code&gt; still asks for a password&lt;&#x2F;h3&gt;
&lt;p&gt;You didn’t use &lt;code&gt;-u&lt;&#x2F;code&gt;. fcrackzip’s CRC check has false positives. Re-run with &lt;code&gt;-u&lt;&#x2F;code&gt; to verify candidates against a real decompression attempt.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;different-files-in-the-same-zip-have-different-passwords&quot;&gt;Different files in the same zip have different passwords&lt;&#x2F;h3&gt;
&lt;p&gt;Classic PKZIP supports per-file passwords. &lt;code&gt;fcrackzip&lt;&#x2F;code&gt; attacks one file at a time; pass &lt;code&gt;-l &amp;lt;filename&amp;gt;&lt;&#x2F;code&gt; (or let it pick the first encrypted one) if you need to be specific.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;fcrackzip-finds-the-password-but-i-still-can-t-unzip&quot;&gt;fcrackzip finds the password, but I still can’t unzip&lt;&#x2F;h3&gt;
&lt;p&gt;Character set &#x2F; locale issue. If the password contains non-ASCII characters, the zip’s creator may have used a different encoding than your terminal. Try &lt;code&gt;unzip -P &quot;$(echo -n &#x27;passwd&#x27; | iconv -t cp437)&quot; archive.zip&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;aes-encrypted-and-dictionary-doesn-t-find-it&quot;&gt;AES-encrypted and dictionary doesn’t find it&lt;&#x2F;h3&gt;
&lt;p&gt;hashcat + rules + a targeted wordlist is the move. See the migration steps above.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;defensive-takeaway&quot;&gt;Defensive takeaway&lt;&#x2F;h2&gt;
&lt;p&gt;If you’re generating zip files you want to stay encrypted: use &lt;strong&gt;AES-256&lt;&#x2F;strong&gt;, not ZipCrypto. Most modern tools (&lt;code&gt;7z&lt;&#x2F;code&gt;, &lt;code&gt;zip&lt;&#x2F;code&gt; from InfoZIP with &lt;code&gt;-e&lt;&#x2F;code&gt;, WinZip, Keka) give you AES if you ask. The default on &lt;code&gt;zip&lt;&#x2F;code&gt; unfortunately is still ZipCrypto on many systems. Check with &lt;code&gt;7z l -slt archive.zip&lt;&#x2F;code&gt; before shipping it.&lt;&#x2F;p&gt;
&lt;p&gt;And pick a password with length, not cleverness. &lt;code&gt;hashcat&lt;&#x2F;code&gt; doesn’t care how weird your substitution pattern is; it only cares about entropy. A four-word passphrase (&lt;code&gt;correct-horse-battery-staple&lt;&#x2F;code&gt;-style, ~44 bits) is fine against offline ZipCrypto attacks; a random 12-character mixed password (~78 bits) is fine against GPU AES attacks. Anything shorter is recoverable.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;faq&quot;&gt;FAQ&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;is-fcrackzip-legal&quot;&gt;Is fcrackzip legal?&lt;&#x2F;h3&gt;
&lt;p&gt;Recovering passwords on files you own or are authorised to access is legal in every jurisdiction I’m aware of. Doing it on someone else’s file without authorisation is unauthorised access — the same law that applies to any other computer intrusion.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;is-fcrackzip-maintained&quot;&gt;Is fcrackzip maintained?&lt;&#x2F;h3&gt;
&lt;p&gt;Barely. The codebase has been stable for fifteen-plus years. For modern work, the combination of &lt;code&gt;zip2john&lt;&#x2F;code&gt; + &lt;code&gt;hashcat&lt;&#x2F;code&gt; has eclipsed it.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;can-fcrackzip-use-a-gpu&quot;&gt;Can fcrackzip use a GPU?&lt;&#x2F;h3&gt;
&lt;p&gt;No. &lt;code&gt;fcrackzip&lt;&#x2F;code&gt; is CPU-only. If GPU is what you have, go straight to hashcat.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;does-fcrackzip-handle-7z-rar-gpg-files&quot;&gt;Does fcrackzip handle .7z, .rar, .gpg files?&lt;&#x2F;h3&gt;
&lt;p&gt;No — only classic PKZIP &lt;code&gt;.zip&lt;&#x2F;code&gt;. For &lt;code&gt;.7z&lt;&#x2F;code&gt;, use &lt;code&gt;7z2john&lt;&#x2F;code&gt; + hashcat (mode 11600). For &lt;code&gt;.rar&lt;&#x2F;code&gt;, &lt;code&gt;rar2john&lt;&#x2F;code&gt; + hashcat (mode 12500 &#x2F; 13000). For &lt;code&gt;.gpg&lt;&#x2F;code&gt;, &lt;code&gt;gpg2john&lt;&#x2F;code&gt; + hashcat (mode 17010+).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;how-do-i-generate-a-targeted-wordlist&quot;&gt;How do I generate a targeted wordlist?&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;code&gt;hashcat&lt;&#x2F;code&gt;’s maskprocessor (&lt;code&gt;mp64&lt;&#x2F;code&gt;), &lt;code&gt;crunch&lt;&#x2F;code&gt;, and &lt;code&gt;cewl&lt;&#x2F;code&gt; (crawls a site for candidate words) are all worth knowing. For recovering a specific forgotten password, writing down what you remember about it — length, likely words, likely numbers — and building a mask attack from that is the highest-yield approach.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fcrackzip&lt;&#x2F;code&gt; + &lt;code&gt;-D&lt;&#x2F;code&gt; + a wordlist is the right first try.&lt;&#x2F;li&gt;
&lt;li&gt;If that doesn’t work and it’s ZipCrypto: &lt;code&gt;fcrackzip&lt;&#x2F;code&gt; + &lt;code&gt;-b&lt;&#x2F;code&gt; with a narrow charset and short length.&lt;&#x2F;li&gt;
&lt;li&gt;If that doesn’t work or it’s AES: &lt;code&gt;zip2john&lt;&#x2F;code&gt; + &lt;code&gt;hashcat&lt;&#x2F;code&gt; on a GPU.&lt;&#x2F;li&gt;
&lt;li&gt;If that doesn’t work: your password was strong enough. That’s the system working as intended.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>pdfcrack — recover lost PDF passwords on Linux and macOS (2026)</title>
        <published>2026-04-22T00:00:00+00:00</published>
        <updated>2026-04-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.x2q.net/post/crack-password-protected-pdf-pdfcrack/"/>
        <id>https://www.x2q.net/post/crack-password-protected-pdf-pdfcrack/</id>
        
        <content type="html" xml:base="https://www.x2q.net/post/crack-password-protected-pdf-pdfcrack/">&lt;p&gt;&lt;strong&gt;TL;DR —&lt;&#x2F;strong&gt; &lt;strong&gt;&lt;code&gt;pdfcrack&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; is a command-line tool that recovers the &lt;strong&gt;user password&lt;&#x2F;strong&gt; or &lt;strong&gt;owner password&lt;&#x2F;strong&gt; of an encrypted PDF by brute-force or dictionary attack. It’s small, written in C, runs on Linux&#x2F;macOS&#x2F;WSL, and handles every encryption profile PDF has shipped — 40-bit RC4, 128-bit RC4, 128-bit AES, 256-bit AES (PDF 1.7 + 2.0). It is &lt;strong&gt;single-threaded and CPU-only&lt;&#x2F;strong&gt;, so it is fast on the weak old RC4-40 encryption but increasingly slow as PDFs get modern. Install with &lt;code&gt;apt install pdfcrack&lt;&#x2F;code&gt; (Debian&#x2F;Ubuntu), &lt;code&gt;brew install pdfcrack&lt;&#x2F;code&gt; (macOS), or build from source. When &lt;code&gt;pdfcrack&lt;&#x2F;code&gt; is too slow, switch to &lt;strong&gt;&lt;code&gt;pdf2john&lt;&#x2F;code&gt; + &lt;code&gt;hashcat&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; on a GPU.&lt;&#x2F;p&gt;
&lt;p&gt;This walkthrough assumes you have &lt;strong&gt;authorisation to recover the password&lt;&#x2F;strong&gt; on the PDF — your own document, a client’s, or a legally-acquired file. Don’t attack PDFs you don’t own.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;user-password-vs-owner-password&quot;&gt;User password vs owner password&lt;&#x2F;h2&gt;
&lt;p&gt;PDF has two passwords:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;User password&lt;&#x2F;strong&gt; — required to &lt;strong&gt;open and read&lt;&#x2F;strong&gt; the document.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Owner password&lt;&#x2F;strong&gt; — required to &lt;strong&gt;remove restrictions&lt;&#x2F;strong&gt; (print, copy, modify) on a document that opens without a user password.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Many “protected” PDFs are only owner-password-restricted: they open fine, but printing and copying are disabled by the reader. Those are trivial to unlock — &lt;code&gt;qpdf --decrypt&lt;&#x2F;code&gt; or any of the &lt;code&gt;pdftk&lt;&#x2F;code&gt;-style tools strip owner-only restrictions without needing to guess anything. &lt;code&gt;pdfcrack&lt;&#x2F;code&gt; is for the harder case: a &lt;strong&gt;user-password-protected PDF you can’t open at all&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Check which you’re dealing with:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;$ qpdf --show-encryption document.pdf
R = 6
P = -3904
User password = 
Supplied password is owner password
extract for accessibility: allowed
extract for any purpose: not allowed
print low resolution: allowed
print high resolution: not allowed
modify document assembly: not allowed
modify forms: not allowed
modify annotations: not allowed
modify other: not allowed
stream encryption method: AESv3
string encryption method: AESv3
file encryption method: AESv3
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;R = ...&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; tells you the revision &#x2F; profile. &lt;code&gt;R=2&lt;&#x2F;code&gt; = RC4-40, &lt;code&gt;R=3&lt;&#x2F;code&gt; = RC4-128, &lt;code&gt;R=4&lt;&#x2F;code&gt; = AES-128, &lt;code&gt;R=5&#x2F;6&lt;&#x2F;code&gt; = AES-256.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;“Supplied password is owner password”&lt;&#x2F;strong&gt; + “User password = (empty)” means you can open it and just need to strip restrictions. Use &lt;code&gt;qpdf --decrypt --password=&#x27;&#x27; input.pdf output.pdf&lt;&#x2F;code&gt; and you’re done.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;install&quot;&gt;Install&lt;&#x2F;h2&gt;
&lt;pre&gt;&lt;code&gt;# Debian &#x2F; Ubuntu &#x2F; Mint &#x2F; Kali
sudo apt install pdfcrack

# macOS (Homebrew)
brew install pdfcrack

# Arch &#x2F; Manjaro
sudo pacman -S pdfcrack

# Fedora &#x2F; RHEL
sudo dnf install pdfcrack

# From source
wget https:&#x2F;&#x2F;sourceforge.net&#x2F;projects&#x2F;pdfcrack&#x2F;files&#x2F;latest&#x2F;download -O pdfcrack.tar.gz
tar xf pdfcrack.tar.gz &amp;amp;&amp;amp; cd pdfcrack-*
make
sudo cp pdfcrack &#x2F;usr&#x2F;local&#x2F;bin&#x2F;
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;benchmark&quot;&gt;Benchmark&lt;&#x2F;h2&gt;
&lt;pre&gt;&lt;code&gt;$ pdfcrack -b
Benchmark:	Average Speed (calls &#x2F; second):
MD5:			1728972.6
MD5_50 (fast):		 97879.3
MD5_50 (slow):		 69167.0

RC4 (40, static):	606555.3
RC4 (40, no check):	598050.0
RC4 (128, no check):	590141.7

Benchmark:	Average Speed (passwords &#x2F; second):
PDF (40, user):		453510.2
PDF (40, owner):	220250.0
PDF (40, owner, fast):	499995.0

PDF (128, user):	 22000.0
PDF (128, owner):	 10408.7
PDF (128, owner, fast):	 22220.0
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Translation:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;RC4-40&lt;&#x2F;strong&gt; (PDF 1.3, very old) — ~450k passwords&#x2F;sec. Brute-force of a full 8-character alphanumeric space: ~1 week on one core.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;RC4-128 &#x2F; AES-128&lt;&#x2F;strong&gt; — ~22k passwords&#x2F;sec. Brute-force becomes unrealistic above length 6 with any charset.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;AES-256 (R=5&#x2F;6)&lt;&#x2F;strong&gt; — even slower; &lt;code&gt;pdfcrack&lt;&#x2F;code&gt; does support it but effectively only for dictionary attacks against small wordlists.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;If you need speed beyond this, you want &lt;code&gt;hashcat&lt;&#x2F;code&gt; on a GPU. Numbers at the bottom.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;dictionary-attack&quot;&gt;Dictionary attack&lt;&#x2F;h2&gt;
&lt;pre&gt;&lt;code&gt;pdfcrack -f document.pdf -w &#x2F;usr&#x2F;share&#x2F;wordlists&#x2F;rockyou.txt
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-f&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — target file.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-w &amp;lt;file&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — wordlist, one candidate per line.&lt;&#x2F;li&gt;
&lt;li&gt;Optional &lt;strong&gt;&lt;code&gt;-o&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — attack the owner password only (ignore user password). Default is to try user password first.&lt;&#x2F;li&gt;
&lt;li&gt;Optional &lt;strong&gt;&lt;code&gt;-u&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — attack user password only.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;If you have context — the password is probably a first name, a year, a project code — assemble a smaller targeted list. Generic lists are a long shot once you’re past the top ten thousand entries.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;brute-force-attack&quot;&gt;Brute-force attack&lt;&#x2F;h2&gt;
&lt;pre&gt;&lt;code&gt;pdfcrack -f document.pdf -c &amp;#39;abcdefghijklmnopqrstuvwxyz0123456789&amp;#39; -n 4 -m 8
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-c &amp;lt;charset&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — character set to try. Default includes a broad ASCII range.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-n &amp;lt;min&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — minimum length.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-m &amp;lt;max&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — maximum length.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Tune &lt;code&gt;-c&lt;&#x2F;code&gt; aggressively. If you know the password is all lowercase, don’t give it uppercase. If you know it has a digit at the end, use a mask attack (hashcat territory, not pdfcrack).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;resume-a-long-run&quot;&gt;Resume a long run&lt;&#x2F;h2&gt;
&lt;pre&gt;&lt;code&gt;pdfcrack -f document.pdf -w rockyou.txt -s &#x2F;tmp&#x2F;state.sav
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;-s&lt;&#x2F;code&gt; writes periodic save files; if you kill pdfcrack and restart, pass &lt;code&gt;-l &#x2F;tmp&#x2F;state.sav&lt;&#x2F;code&gt; to resume.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;when-to-switch-to-hashcat&quot;&gt;When to switch to hashcat&lt;&#x2F;h2&gt;
&lt;p&gt;For any modern PDF (R=4, 5, or 6 — i.e. AES-128 or AES-256), &lt;code&gt;pdfcrack&lt;&#x2F;code&gt;’s single-core speed becomes the bottleneck. &lt;code&gt;pdf2john&lt;&#x2F;code&gt; + &lt;code&gt;hashcat&lt;&#x2F;code&gt; gets you two to three orders of magnitude more throughput on a GPU.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;step-1-extract-the-hash&quot;&gt;Step 1: extract the hash&lt;&#x2F;h3&gt;
&lt;pre&gt;&lt;code&gt;$ &#x2F;usr&#x2F;share&#x2F;john&#x2F;pdf2john.pl document.pdf &amp;gt; hash.txt
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;(On Debian&#x2F;Ubuntu: &lt;code&gt;apt install john&lt;&#x2F;code&gt; provides &lt;code&gt;pdf2john.pl&lt;&#x2F;code&gt;.)&lt;&#x2F;p&gt;
&lt;h3 id=&quot;step-2-identify-the-hash-mode&quot;&gt;Step 2: identify the hash mode&lt;&#x2F;h3&gt;
&lt;pre&gt;&lt;code&gt;$ head -1 hash.txt
document.pdf:$pdf$5*6*256*-1028*1*16*...
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Map the first two numbers:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$pdf$1*2*...&lt;&#x2F;code&gt; → &lt;strong&gt;mode 10400&lt;&#x2F;strong&gt; (PDF 1.1–1.3, RC4-40, user)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;$pdf$1*2*...&lt;&#x2F;code&gt; with owner salt → &lt;strong&gt;mode 10410 &#x2F; 10420&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;$pdf$2*3*...&lt;&#x2F;code&gt; → &lt;strong&gt;mode 10500&lt;&#x2F;strong&gt; (PDF 1.4–1.6, RC4-128 &#x2F; AES-128)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;$pdf$5*5*...&lt;&#x2F;code&gt; → &lt;strong&gt;mode 10600&lt;&#x2F;strong&gt; (PDF 1.7 r=5, AES-256)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;$pdf$5*6*...&lt;&#x2F;code&gt; → &lt;strong&gt;mode 10700&lt;&#x2F;strong&gt; (PDF 1.7 r=6 &#x2F; PDF 2.0, AES-256 with PBKDF2)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;step-3-run-hashcat&quot;&gt;Step 3: run hashcat&lt;&#x2F;h3&gt;
&lt;pre&gt;&lt;code&gt;hashcat -m 10700 hash.txt &#x2F;path&#x2F;to&#x2F;wordlist.txt
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;On a single modern GPU (e.g. RTX 4090):&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mode 10400 (RC4-40):&lt;&#x2F;strong&gt; ~10 billion H&#x2F;s — any practical password is recovered instantly.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Mode 10500 (RC4-128 &#x2F; AES-128):&lt;&#x2F;strong&gt; ~100 million H&#x2F;s.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Mode 10700 (AES-256, PBKDF2):&lt;&#x2F;strong&gt; ~50,000 H&#x2F;s. This is by design — the PBKDF2 iterations make brute-force expensive.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;For mode 10700, your attack strategy matters more than raw speed. Dictionary + rules + mask attacks targeted at likely password patterns are orders of magnitude more productive than exhaustive brute-force.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;common-gotchas&quot;&gt;Common gotchas&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;file-seems-encrypted-but-i-can-still-open-it-without-a-password&quot;&gt;“File seems encrypted but I can still open it without a password”&lt;&#x2F;h3&gt;
&lt;p&gt;You’re looking at owner-only restrictions. Use &lt;code&gt;qpdf --decrypt input.pdf output.pdf&lt;&#x2F;code&gt; — no cracking needed.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;pdfcrack-runs-but-never-finds-anything&quot;&gt;pdfcrack runs but never finds anything&lt;&#x2F;h3&gt;
&lt;p&gt;Three likely causes:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Password is longer &#x2F; more complex than your charset × length covers.&lt;&#x2F;strong&gt; Increase &lt;code&gt;-m&lt;&#x2F;code&gt; and&#x2F;or expand &lt;code&gt;-c&lt;&#x2F;code&gt;, or switch to a dictionary.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Password contains non-ASCII characters.&lt;&#x2F;strong&gt; pdfcrack’s charset is ASCII by default; non-ASCII user passwords in old PDFs used varying encodings (PDFDocEncoding, UTF-16). Try &lt;code&gt;pdf2john&lt;&#x2F;code&gt; + hashcat, which handles the encoding properly.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;It’s AES-256 (R=6) and you’re being patient.&lt;&#x2F;strong&gt; See speed notes above — realistic only with a dictionary.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h3 id=&quot;only-aes-256-is-supported-error-or-similar&quot;&gt;“Only AES-256 is supported” error or similar&lt;&#x2F;h3&gt;
&lt;p&gt;Your &lt;code&gt;pdfcrack&lt;&#x2F;code&gt; build is old. Ubuntu LTS sometimes ships a version that doesn’t fully handle R=6. Build from source or switch to &lt;code&gt;pdf2john&lt;&#x2F;code&gt; + &lt;code&gt;hashcat&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;pdf-opens-but-printing-copying-is-blocked&quot;&gt;PDF opens but printing&#x2F;copying is blocked&lt;&#x2F;h3&gt;
&lt;p&gt;That’s owner-password-restricted only. &lt;code&gt;qpdf --decrypt&lt;&#x2F;code&gt; without needing a password usually works:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;qpdf --decrypt --password=&amp;#39;&amp;#39; input.pdf output.pdf
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;defensive-takeaway&quot;&gt;Defensive takeaway&lt;&#x2F;h2&gt;
&lt;p&gt;For anything you want to stay encrypted in 2026, use &lt;strong&gt;AES-256 with a PDF 2.0 (R=6) password&lt;&#x2F;strong&gt;. That’s the profile with PBKDF2 key derivation that makes offline attacks expensive on current hardware.&lt;&#x2F;p&gt;
&lt;p&gt;And, as always, the password’s length matters more than anything else. A 20-character random passphrase is brute-force-intractable against any current GPU. A 6-character “clever substitution” password is a coffee break on an RTX 4090.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;faq&quot;&gt;FAQ&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;is-pdfcrack-still-maintained&quot;&gt;Is pdfcrack still maintained?&lt;&#x2F;h3&gt;
&lt;p&gt;Updated slowly. The codebase handles all currently shipped PDF encryption profiles; it just isn’t where performance work is happening anymore (that’s hashcat).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;does-pdfcrack-use-gpu&quot;&gt;Does pdfcrack use GPU?&lt;&#x2F;h3&gt;
&lt;p&gt;No. CPU-only, single-threaded. For GPU work, use &lt;code&gt;pdf2john&lt;&#x2F;code&gt; + &lt;code&gt;hashcat&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;can-pdfcrack-recover-the-document-if-the-creator-cleared-the-user-password-but-kept-the-owner-password&quot;&gt;Can pdfcrack recover the document if the creator cleared the user password but kept the owner password?&lt;&#x2F;h3&gt;
&lt;p&gt;Yes — use &lt;code&gt;qpdf --decrypt&lt;&#x2F;code&gt; first; if that won’t fully decrypt, &lt;code&gt;pdfcrack -o&lt;&#x2F;code&gt; attacks the owner password.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;does-pdfcrack-leak-my-pdf-anywhere&quot;&gt;Does pdfcrack leak my PDF anywhere?&lt;&#x2F;h3&gt;
&lt;p&gt;No. It’s local. Anything online-only (“crack my PDF at cloud-service-X.com”) involves uploading the file, which you shouldn’t do for confidential material.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-if-i-only-vaguely-remember-the-password&quot;&gt;What if I only vaguely remember the password?&lt;&#x2F;h3&gt;
&lt;p&gt;Write down what you remember — likely words, likely digits, likely length — and build a targeted wordlist or &lt;code&gt;hashcat&lt;&#x2F;code&gt; mask (&lt;code&gt;-a 3 ?l?l?l?l?l?d?d?d?d&lt;&#x2F;code&gt;). That beats any generic list.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;can-i-recover-text-from-an-aes-256-pdf-without-the-password&quot;&gt;Can I recover text from an AES-256 PDF without the password?&lt;&#x2F;h3&gt;
&lt;p&gt;No. AES-256 with PBKDF2 (R=6) is, to the best of public cryptanalysis, not broken. If the password is strong and lost, the contents are lost.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Owner-password-only?&lt;&#x2F;strong&gt; &lt;code&gt;qpdf --decrypt&lt;&#x2F;code&gt;, no cracking needed.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;User-password-protected, old (R=2&#x2F;3)?&lt;&#x2F;strong&gt; &lt;code&gt;pdfcrack&lt;&#x2F;code&gt; with a dictionary works fine.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Modern (R=4&#x2F;5&#x2F;6)?&lt;&#x2F;strong&gt; &lt;code&gt;pdf2john&lt;&#x2F;code&gt; + &lt;code&gt;hashcat&lt;&#x2F;code&gt; on a GPU, targeted attack.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Strong 20-character random?&lt;&#x2F;strong&gt; Accept the loss.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Hacking Wi-Fi across 25 years — 2002, 2007, 2012, 2017, 2022, 2027</title>
        <published>2026-04-22T00:00:00+00:00</published>
        <updated>2026-04-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.x2q.net/post/hack-wireless-network/"/>
        <id>https://www.x2q.net/post/hack-wireless-network/</id>
        
        <content type="html" xml:base="https://www.x2q.net/post/hack-wireless-network/">&lt;p&gt;&lt;strong&gt;TL;DR —&lt;&#x2F;strong&gt; Wi-Fi security has gone through four generations — &lt;strong&gt;WEP&lt;&#x2F;strong&gt;, &lt;strong&gt;WPA&lt;&#x2F;strong&gt;, &lt;strong&gt;WPA2&lt;&#x2F;strong&gt;, &lt;strong&gt;WPA3&lt;&#x2F;strong&gt; — each introduced because its predecessor was broken. This post walks through six snapshots of the state of the art: &lt;strong&gt;2002&lt;&#x2F;strong&gt; (WEP, trivial), &lt;strong&gt;2007&lt;&#x2F;strong&gt; (WEP dead, WPA widespread, offline attacks emerging), &lt;strong&gt;2012&lt;&#x2F;strong&gt; (WPA2 dominant, dictionary attacks on GPUs), &lt;strong&gt;2017&lt;&#x2F;strong&gt; (KRACK breaks WPA2 under the right conditions, PMKID leak in 2018), &lt;strong&gt;2022&lt;&#x2F;strong&gt; (WPA3 rolling out, downgrade attacks, cloud-scale cracking), &lt;strong&gt;2027&lt;&#x2F;strong&gt; (today’s landscape: WPA3 nearly universal, 6 GHz opens new surface, attacks move up the stack). For each year I summarise the attack, the tool, and what a defender should have been doing. Authorised testing only — the defensive guidance at the bottom is the point.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;at-a-glance&quot;&gt;At a glance&lt;&#x2F;h2&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Year&lt;&#x2F;th&gt;&lt;th&gt;Dominant standard&lt;&#x2F;th&gt;&lt;th&gt;Headline attack&lt;&#x2F;th&gt;&lt;th&gt;Effort to compromise a typical network&lt;&#x2F;th&gt;&lt;th&gt;Fastest dictionary &#x2F; key-rate at the time&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;2002&lt;&#x2F;td&gt;&lt;td&gt;WEP (100%)&lt;&#x2F;td&gt;&lt;td&gt;FMS statistical IV&lt;&#x2F;td&gt;&lt;td&gt;~4M packets &#x2F; 2–10 h on a busy AP&lt;&#x2F;td&gt;&lt;td&gt;N&#x2F;A — key recovered from IVs&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;2007&lt;&#x2F;td&gt;&lt;td&gt;WEP 35%, WPA 25%, WPA2 20%, open 20%&lt;&#x2F;td&gt;&lt;td&gt;PTW + aircrack-ng&lt;&#x2F;td&gt;&lt;td&gt;40–85k packets &#x2F; &amp;lt;5 min (WEP)&lt;&#x2F;td&gt;&lt;td&gt;~50–100 PSK&#x2F;s (CPU, cowpatty)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;2012&lt;&#x2F;td&gt;&lt;td&gt;WPA2 ~70%&lt;&#x2F;td&gt;&lt;td&gt;Reaver (WPS) + GPU PSK dictionary&lt;&#x2F;td&gt;&lt;td&gt;11,000 WPS PINs &#x2F; 2–10 h&lt;&#x2F;td&gt;&lt;td&gt;~75,000 PSK&#x2F;s (GTX 580)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;2017&lt;&#x2F;td&gt;&lt;td&gt;WPA2 ~85%&lt;&#x2F;td&gt;&lt;td&gt;KRACK; then PMKID (2018)&lt;&#x2F;td&gt;&lt;td&gt;1 packet (PMKID) → offline dict&lt;&#x2F;td&gt;&lt;td&gt;~400,000 PSK&#x2F;s (GTX 1080)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;2022&lt;&#x2F;td&gt;&lt;td&gt;WPA2 ~60%, WPA3 ~20%, mixed ~15%&lt;&#x2F;td&gt;&lt;td&gt;Dragonblood downgrade&lt;&#x2F;td&gt;&lt;td&gt;Force WPA2 fallback, then GPU&#x2F;cloud&lt;&#x2F;td&gt;&lt;td&gt;~1.2M PSK&#x2F;s (RTX 3090), ~10M (8× A100)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;2027&lt;&#x2F;td&gt;&lt;td&gt;WPA3 ~40%, mixed ~35%, WPA2 ~20%&lt;&#x2F;td&gt;&lt;td&gt;Client-side + downgrade + chipset FW&lt;&#x2F;td&gt;&lt;td&gt;SAE intact; legacy WPA2 on borrowed time&lt;&#x2F;td&gt;&lt;td&gt;~5–8M PSK&#x2F;s (RTX 5090-era single GPU)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Approximate global deployment shares are from public Wi-Fi surveys (WiGLE aggregate trends + vendor reports); treat as ± 10 points.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;2002-wep-everywhere-and-trivially-broken&quot;&gt;2002 — WEP everywhere, and trivially broken&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Standard:&lt;&#x2F;strong&gt; IEEE 802.11b with &lt;strong&gt;WEP&lt;&#x2F;strong&gt; (Wired Equivalent Privacy). 40-bit or 104-bit RC4 key, 24-bit IV, CRC-32 integrity.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;What’s wrong with it:&lt;&#x2F;strong&gt; The IV is too small, keys are static, and RC4’s initial key schedule leaks bits of the key when certain IVs are used. These are the &lt;strong&gt;FMS attack&lt;&#x2F;strong&gt; (Fluhrer, Mantin, Shamir, 2001) and its descendants.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;What an attacker did in 2002:&lt;&#x2F;strong&gt; captured traffic with a Prism-based USB adapter, collected a few million packets (hours on an active network), and extracted the key with &lt;strong&gt;AirSnort&lt;&#x2F;strong&gt; or &lt;strong&gt;WEPCrack&lt;&#x2F;strong&gt;. Attacks were so embarrassing that Wi-Fi Alliance pushed WPA as a temporary firmware-patchable fix in 2003.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;By the numbers (2002):&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Global Wi-Fi deployment:&lt;&#x2F;strong&gt; ~100% WEP (WPA not ratified until March 2003).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Key length:&lt;&#x2F;strong&gt; 40-bit (export-grade) or 104-bit. Irrelevant — the attack is on the IV, not the key.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;IV space:&lt;&#x2F;strong&gt; 24 bits → only ~16.7 M possible IVs. Birthday-paradox collision around ~5,000 packets.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;FMS attack packet count:&lt;&#x2F;strong&gt; ~500 k–4 M “interesting” IV frames (a few hours of active traffic).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Typical crack wall-clock:&lt;&#x2F;strong&gt; 2–10 h on a busy SOHO network.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Consumer hardware required:&lt;&#x2F;strong&gt; one laptop + one Prism2&#x2F;Prism2.5 USB dongle (~$40 used at the time).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Tools:&lt;&#x2F;strong&gt; AirSnort, WEPCrack, Kismet for capture.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;CVEs:&lt;&#x2F;strong&gt; WEP itself isn’t a CVE, but the 2001 Borisov&#x2F;Goldberg&#x2F;Wagner paper and the 2001 FMS paper are the canonical breakage references.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;strong&gt;What a defender should have done:&lt;&#x2F;strong&gt; realise WEP is not a security feature and treat the wireless LAN as untrusted. Use a VPN or IPSec at layer 3.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;2007-wep-dead-wpa-transitional-wpa2-arriving&quot;&gt;2007 — WEP dead, WPA transitional, WPA2 arriving&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Standard:&lt;&#x2F;strong&gt; WPA (2003), a stopgap with per-packet keys (TKIP). WPA2 (2004) with AES-CCMP starting to appear in home routers.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;What’s new for attackers:&lt;&#x2F;strong&gt; the &lt;strong&gt;PTW attack&lt;&#x2F;strong&gt; on WEP (2007) drops the packet count needed from ~1 million to ~40,000 — WEP now cracks in minutes. &lt;strong&gt;aircrack-ng&lt;&#x2F;strong&gt; becomes the canonical toolkit. Against WPA-Personal, offline dictionary attacks on the 4-way-handshake become practical on CPUs: capture a handshake, feed the PSK derivation through &lt;code&gt;cowpatty&lt;&#x2F;code&gt; or &lt;code&gt;aircrack-ng&lt;&#x2F;code&gt;, try dictionary entries one at a time.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;What attackers didn’t have yet:&lt;&#x2F;strong&gt; meaningful GPU acceleration of WPA-PSK. Dictionary attacks were slow.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;By the numbers (2007):&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Global deployment (WiGLE-style surveys):&lt;&#x2F;strong&gt; WEP ~35%, WPA ~25%, WPA2 ~20%, open ~20%.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;PTW attack packet count:&lt;&#x2F;strong&gt; ~40,000–85,000 captured frames to recover a WEP key (down from millions).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Crack time with aircrack-ng + packet injection:&lt;&#x2F;strong&gt; &amp;lt;5 minutes, often under 60 seconds.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;WPA-PSK dictionary rate (CPU, cowpatty&#x2F;aircrack-ng):&lt;&#x2F;strong&gt; ~50–100 candidates&#x2F;sec per core.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Rockyou wordlist&lt;&#x2F;strong&gt; (leaked Dec 2009): 14.3 M entries — became the de-facto English PSK wordlist.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;WPA2 mandatory for Wi-Fi CERTIFIED:&lt;&#x2F;strong&gt; March 2006.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Aircrack-ng 1.0:&lt;&#x2F;strong&gt; released 2006, consolidating the ecosystem.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;strong&gt;What a defender should have done:&lt;&#x2F;strong&gt; migrate to WPA2-AES-CCMP, pick a PSK longer than any dictionary. Drop WEP networks entirely.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;2012-wpa2-dominant-gpu-cracking-mature&quot;&gt;2012 — WPA2 dominant, GPU-cracking mature&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Standard:&lt;&#x2F;strong&gt; WPA2 is the default on essentially all consumer gear. WPA2-Enterprise (802.1X &#x2F; EAP) in corporate networks.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;What’s new for attackers:&lt;&#x2F;strong&gt; &lt;strong&gt;hashcat&lt;&#x2F;strong&gt; and &lt;strong&gt;oclHashcat&lt;&#x2F;strong&gt;, &lt;strong&gt;pyrit&lt;&#x2F;strong&gt;, and &lt;strong&gt;John the Ripper&lt;&#x2F;strong&gt; turn the WPA-PSK dictionary attack into a &lt;strong&gt;GPU problem&lt;&#x2F;strong&gt;. A handshake capture + a single modern GPU does tens of thousands of candidate PSKs per second; a small rented GPU farm handles hundreds of thousands. &lt;strong&gt;airodump-ng&lt;&#x2F;strong&gt; + &lt;strong&gt;Reaver&lt;&#x2F;strong&gt; (2011) exploit a &lt;strong&gt;WPS PIN&lt;&#x2F;strong&gt; implementation flaw to recover the WPA-PSK without a dictionary at all — a dealbreaker for home routers with WPS-on-by-default.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;By the numbers (2012):&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Global deployment:&lt;&#x2F;strong&gt; WPA2 ~70%, WEP ~10%, open ~15%, WPA ~5%.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;WPS PIN space on paper:&lt;&#x2F;strong&gt; 10⁸ = 100 million. &lt;strong&gt;Effective&lt;&#x2F;strong&gt; attack space after the Viehböck split: 10⁴ + 10³ = ~11,000 tries.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Reaver wall-clock to recover WPS PIN → PSK:&lt;&#x2F;strong&gt; 2–10 hours against an unpatched AP.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;hashcat WPA-PSK throughput on GTX 580 (reference 2012 GPU):&lt;&#x2F;strong&gt; ~75,000 candidates&#x2F;sec.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;hashcat WPA-PSK on a small 4-GPU rig of the era:&lt;&#x2F;strong&gt; ~300,000 candidates&#x2F;sec, i.e. &lt;code&gt;rockyou.txt&lt;&#x2F;code&gt; (14.3 M) in ~50 seconds.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Real-world PSK recovery rate with rockyou + common rules:&lt;&#x2F;strong&gt; ~20–25% on captured handshakes in academic studies.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;WPA2-PEAP&#x2F;MSCHAPv2 hash rate (a Wi-Fi-adjacent Enterprise pain point):&lt;&#x2F;strong&gt; tens of millions&#x2F;sec on a single GPU, full 2²⁸ keyspace within a day.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;strong&gt;What defenders should have done:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Turn off WPS.&lt;&#x2F;strong&gt; Most routers had it on by default.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Use long random PSKs&lt;&#x2F;strong&gt;, not dictionary words.&lt;&#x2F;li&gt;
&lt;li&gt;For enterprise: &lt;strong&gt;802.1X + EAP-TLS with client certificates&lt;&#x2F;strong&gt;, not PEAP-MSCHAPv2 (which is itself a GPU problem).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;2017-krack&quot;&gt;2017 — KRACK&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Standard:&lt;&#x2F;strong&gt; WPA2 still everywhere.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;What’s new for attackers:&lt;&#x2F;strong&gt; &lt;strong&gt;KRACK&lt;&#x2F;strong&gt; (Vanhoef, Piessens, 2017) — Key Reinstallation Attack. It doesn’t crack the PSK. It exploits a subtle flaw in the 4-way handshake where, under certain conditions (specifically on Android 6+ and wpa_supplicant 2.4–2.6), replaying handshake message 3 causes the client to reinstall an &lt;strong&gt;all-zero session key&lt;&#x2F;strong&gt;. From there, the attacker can decrypt selected traffic and, depending on cipher suite, inject packets.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;What made KRACK different from prior attacks:&lt;&#x2F;strong&gt; it hit the &lt;strong&gt;protocol, not the key&lt;&#x2F;strong&gt;. A strong PSK didn’t help. Every WPA2 implementation needed a patch. Most vendors shipped one within weeks; some took years.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Also in this era:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PMKID hashcat attack&lt;&#x2F;strong&gt; (2018, Steube). Many APs leak the PMKID in a single message; one packet is enough to start an offline PSK attack. Lowered the capture bar further — no client association needed.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Evil twin &#x2F; captive portal phishing.&lt;&#x2F;strong&gt; As device UIs got prettier, fake captive portals became alarmingly effective against humans.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;strong&gt;By the numbers (2017–2018):&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Global deployment:&lt;&#x2F;strong&gt; WPA2 ~85%, WEP &amp;lt;3%, open ~10%, WPA3 not yet shipping.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;KRACK CVEs:&lt;&#x2F;strong&gt; 10 in the coordinated disclosure (CVE-2017-13077 through CVE-2017-13088).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;KRACK-affected devices:&lt;&#x2F;strong&gt; all WPA2 clients and APs — &lt;strong&gt;10+ billion&lt;&#x2F;strong&gt; endpoints. Real patching took years for long-tail IoT.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Vendor patch latency:&lt;&#x2F;strong&gt; Microsoft &#x2F; most Linux distros within 1 week of disclosure; Android 2 months+ depending on OEM; many embedded devices never patched.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;PMKID attack packet count:&lt;&#x2F;strong&gt; &lt;strong&gt;1 single frame&lt;&#x2F;strong&gt; vs. the 4-way handshake’s 4 frames — no client association or deauth needed.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;hashcat on GTX 1080 (reference 2017 GPU):&lt;&#x2F;strong&gt; ~400,000 WPA-PSK candidates&#x2F;sec — ~5× faster than 2012.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;A rented 8× 1080 Ti instance (~$4&#x2F;h in 2017):&lt;&#x2F;strong&gt; ~3M candidates&#x2F;sec — &lt;code&gt;rockyou.txt&lt;&#x2F;code&gt; in &amp;lt;5 seconds.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Coordinated disclosure lead time:&lt;&#x2F;strong&gt; 4 months from vendor notification to public release.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;strong&gt;What a defender should have done:&lt;&#x2F;strong&gt; patch Android &#x2F; wpa_supplicant immediately, enforce HTTPS everywhere so decrypted Wi-Fi traffic is less useful, start planning WPA3 migration.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;2022-wpa3-rolling-out-downgrade-attacks&quot;&gt;2022 — WPA3 rolling out, downgrade attacks&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Standard:&lt;&#x2F;strong&gt; WPA3 (2018) required for Wi-Fi CERTIFIED devices since 2020. Key upgrades:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SAE&lt;&#x2F;strong&gt; (Simultaneous Authentication of Equals) replaces the 4-way handshake PSK — offline dictionary attacks become infeasible.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;192-bit mode&lt;&#x2F;strong&gt; for Enterprise.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Forward secrecy&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;PMF&lt;&#x2F;strong&gt; (Protected Management Frames) mandatory — neutralises deauth-based denial-of-service.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;strong&gt;What’s new for attackers:&lt;&#x2F;strong&gt; WPA3 isn’t broken, but its deployment mostly isn’t WPA3-&lt;strong&gt;only&lt;&#x2F;strong&gt;. &lt;strong&gt;WPA2&#x2F;3 transition mode&lt;&#x2F;strong&gt; — the default on most APs — means an attacker can force a downgrade and attack as WPA2. This is &lt;strong&gt;Dragonblood&lt;&#x2F;strong&gt; (Vanhoef, Ronen, 2019 + follow-ups): a family of side-channel and downgrade attacks against SAE. By 2022, patched SAE implementations mostly closed the timing side-channels; the downgrade attack still works on mixed-mode networks.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Also in this era:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cloud-scale cracking&lt;&#x2F;strong&gt;: renting 8× H100 or similar on an hourly basis makes PMK dictionary attacks practical at a scale no amateur had in 2012.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Chipset firmware vulnerabilities&lt;&#x2F;strong&gt; (FragAttacks, 2021): flaws in the Wi-Fi stack itself, below the WPA layer, let attackers inject frames without needing any handshake break at all.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;strong&gt;By the numbers (2022):&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Global deployment:&lt;&#x2F;strong&gt; WPA2-only ~60%, WPA3 ~20%, WPA2&#x2F;3 transition mode ~15%, WEP &amp;lt;1%.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;New-router WPA3 support:&lt;&#x2F;strong&gt; ~60% of consumer APs shipped in 2022 supported WPA3 (vs. ~10% in 2020).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Dragonblood CVEs:&lt;&#x2F;strong&gt; 5 (CVE-2019-9494 through CVE-2019-9499).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;FragAttacks CVEs:&lt;&#x2F;strong&gt; 12 in the 2021 bundle.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;hashcat WPA-PSK on RTX 3090:&lt;&#x2F;strong&gt; ~1.2M candidates&#x2F;sec.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;8× A100 cloud instance:&lt;&#x2F;strong&gt; ~10M candidates&#x2F;sec aggregate. &lt;code&gt;rockyou.txt&lt;&#x2F;code&gt; in ~1.5 seconds; a 10B-candidate mask attack in ~17 minutes.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Cost per billion PSK attempts on spot cloud GPUs:&lt;&#x2F;strong&gt; ~$3–5.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;12-character fully-random PSK entropy:&lt;&#x2F;strong&gt; 2⁷² ≈ 4.7 × 10²¹. At 10M H&#x2F;s, exhaustive attack = &lt;strong&gt;~15,000 years&lt;&#x2F;strong&gt;. Safe.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;8-character lowercase+digit PSK entropy:&lt;&#x2F;strong&gt; 2⁴¹ ≈ 2.2 × 10¹². At 10M H&#x2F;s, &lt;strong&gt;~2.5 days&lt;&#x2F;strong&gt;. Not safe.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;strong&gt;What a defender should have done:&lt;&#x2F;strong&gt; WPA3-Personal only (no WPA2 transition) on networks where you can enforce it; PMF required; long random PSK even with SAE; keep AP firmware current.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;2027-today-s-landscape&quot;&gt;2027 — today’s landscape&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Standard:&lt;&#x2F;strong&gt; Wi-Fi 6E (6 GHz) and early Wi-Fi 7 deployments. WPA3 near-universal on new gear; legacy WPA2 still common in small businesses and long-lived home networks.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;State of the art in attacks:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SAE is holding up.&lt;&#x2F;strong&gt; No public, practical attack against a well-configured WPA3-Personal network in 2027. Dictionary attacks against the PSK are not feasible — that’s SAE’s point.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Downgrade attacks&lt;&#x2F;strong&gt; are the main thing. Any network still advertising WPA2&#x2F;3 transition is attackable as WPA2. Adversaries look for mixed-mode SSIDs first.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;6 GHz opens new surface.&lt;&#x2F;strong&gt; The 6 GHz band requires WPA3 for new standards; but legacy client bridging, IoT devices without 6 GHz support, and misconfigured co-broadcast SSIDs create inconsistencies attackers exploit.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Client-side attacks.&lt;&#x2F;strong&gt; When the link layer is secure, attackers move up. Rogue captive portals that trick phones into trusting a bad certificate, QR-code joining with fake SSIDs, deauth-during-roaming attacks (harder now with PMF but not eliminated).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Supply chain.&lt;&#x2F;strong&gt; Firmware-embedded backdoors and vendor bugs in Wi-Fi chipsets (the FragAttacks legacy) remain a durable source of exploits.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Cloud cracking is commoditised.&lt;&#x2F;strong&gt; A weekend-long SAE-offline attack (for the cases where offline attack is possible, i.e. the target ran an unpatched SAE) costs roughly the price of a nice dinner. This mostly doesn’t matter because SAE isn’t reducible to offline dictionary attack — but it shifts economics on legacy WPA2 networks that people haven’t migrated.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;strong&gt;Tools still in rotation in 2027:&lt;&#x2F;strong&gt; aircrack-ng (older, still canonical for captures and WPA2), hashcat (cracking), bettercap (Wi-Fi MITM &#x2F; rogue AP), airgeddon (workflow glue), and a wave of ESP32-based pocket tools for opportunistic deauth and handshake capture.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;By the numbers (2027):&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Global deployment:&lt;&#x2F;strong&gt; WPA3 ~40%, WPA2&#x2F;3 transition ~35%, WPA2-only ~20%, WEP ~0.5% (long tail of ancient gear).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Enterprise share running WPA3-Enterprise 192-bit mode:&lt;&#x2F;strong&gt; still &amp;lt;10%. Most corporate networks remain WPA2-Enterprise with PEAP.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;New-device WPA3 certification:&lt;&#x2F;strong&gt; mandatory for Wi-Fi 6 since 2020, Wi-Fi 6E (6 GHz) since 2022, Wi-Fi 7 at launch.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;hashcat WPA-PSK on RTX 5090-class single GPU (2025+):&lt;&#x2F;strong&gt; ~5–8M candidates&#x2F;sec.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;8× H100 cloud instance:&lt;&#x2F;strong&gt; ~50M candidates&#x2F;sec aggregate. &lt;code&gt;rockyou.txt&lt;&#x2F;code&gt; in ~0.3 seconds.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;SAE (WPA3-Personal) offline attack:&lt;&#x2F;strong&gt; infeasible at any hash rate — the handshake does not release a crackable hash to an eavesdropper.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Cost to brute-force a weak 8-char WPA2 PSK in 2027:&lt;&#x2F;strong&gt; &amp;lt;$10 on spot cloud GPUs.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Cost to brute-force a 12-char random WPA2 PSK:&lt;&#x2F;strong&gt; still ~ $10⁹ (same math as 2022, GPUs 5× faster → still millennia).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;ESP32-S3 “Wi-Fi Nugget” &#x2F; Flipper Zero-class pocket tools&lt;&#x2F;strong&gt; in circulation in 2027: hundreds of thousands of units. Opportunistic handshake capture is trivial; PSK cracking is still offline on a beefy host.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Deauth-DoS effectiveness:&lt;&#x2F;strong&gt; near-zero on PMF-required networks; still works against the ~30% of deployed APs where PMF is “capable” but not “required”.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Bottom line: against &lt;strong&gt;WPA3-Personal with SAE, PMF required, and a random PSK&lt;&#x2F;strong&gt;, none of the 2027 toolkit gets you in via the link layer. Against &lt;strong&gt;a typical 2019-vintage home router still running WPA2 with a weak PSK and WPS optionally exposed&lt;&#x2F;strong&gt;, a weekend on a mid-range GPU is plenty.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;defensive-guidance-for-2027&quot;&gt;Defensive guidance for 2027&lt;&#x2F;h2&gt;
&lt;p&gt;This is the part that matters more than any of the history.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;WPA3-Personal SAE-only, or WPA3-Enterprise with EAP-TLS.&lt;&#x2F;strong&gt; No transition mode on any network you can control. Check explicitly — many APs default to transition.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;PMF required, not optional.&lt;&#x2F;strong&gt; Protected Management Frames prevent deauth-driven handshake captures and basic denial-of-service.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;PSK length: random, ≥ 20 characters.&lt;&#x2F;strong&gt; SAE makes short PSKs safer than they used to be, but no reason to gamble.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Disable WPS permanently.&lt;&#x2F;strong&gt; Even though the WPS-PIN attack is ancient, many routers still ship with it enabled for “convenience”.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Guest SSID isolated.&lt;&#x2F;strong&gt; Client-to-client isolation on and routed to a separate VLAN with no internal LAN access.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;IoT SSID, separate and cloistered.&lt;&#x2F;strong&gt; Cheap Wi-Fi thermostats and toys are the weakest link on most home networks. They get their own SSID, their own VLAN, and no route to anything important.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Firmware updates on APs.&lt;&#x2F;strong&gt; Most of the interesting 2020s Wi-Fi vulnerabilities were chipset-level; the fix is always a firmware update. Buy APs from a vendor that ships them.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Assume the link layer will fail and defend above it.&lt;&#x2F;strong&gt; HTTPS everywhere, client certs on anything that matters, VPN or Tailscale for remote access. When (not if) someone breaks your Wi-Fi, layer 4+ should still hold.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;what-stayed-the-same-across-25-years&quot;&gt;What stayed the same across 25 years&lt;&#x2F;h2&gt;
&lt;p&gt;Three patterns repeat at every generation:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The cryptography is almost always stronger than the deployments.&lt;&#x2F;strong&gt; WEP was a design failure; WPA2 and WPA3 have been broken by implementation flaws, downgrade paths, and side channels — not by pure crypto weaknesses.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Defaults matter more than capabilities.&lt;&#x2F;strong&gt; WPS on by default, WPA2&#x2F;3 transition mode on by default, guest networks without isolation by default — this is what attackers actually find, at scale.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;The attacker always moves up the stack.&lt;&#x2F;strong&gt; When link-layer security improves, attacks shift to captive portals, rogue certs, client mis-configurations, and phishing. No amount of WPA3 fixes the user who clicks “Trust” on the evil twin’s fake certificate.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;coda&quot;&gt;Coda&lt;&#x2F;h2&gt;
&lt;p&gt;If this post’s ancestor on this site, from 2010, was “How to Hack a Wireless Network” and focused on WEP with &lt;code&gt;aircrack-ng&lt;&#x2F;code&gt;, the 2027 version is closer to “How to Think About Wi-Fi Security” and focuses on configuration, patching, and realistic threat modelling. The cryptography has done its job. The layers around it are the interesting frontier now.&lt;&#x2F;p&gt;
&lt;p&gt;Test your own networks — or networks you’re authorised to test — don’t test anyone else’s. The same law that applied in 2002 applies in 2027.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>nemboks.dk — forward your Danish digital post (mit.dk, e-Boks) to your email inbox</title>
        <published>2026-04-22T00:00:00+00:00</published>
        <updated>2026-04-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.x2q.net/post/nemboks-dk-digital-post-forwarding/"/>
        <id>https://www.x2q.net/post/nemboks-dk-digital-post-forwarding/</id>
        
        <content type="html" xml:base="https://www.x2q.net/post/nemboks-dk-digital-post-forwarding/">&lt;p&gt;&lt;strong&gt;TL;DR —&lt;&#x2F;strong&gt; &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;nemboks.dk&quot;&gt;nemboks.dk&lt;&#x2F;a&gt; is a service that &lt;strong&gt;forwards Danish digital post&lt;&#x2F;strong&gt; (from &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;mit.dk&quot;&gt;mit.dk&lt;&#x2F;a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.e-boks.com&#x2F;&quot;&gt;e-Boks&lt;&#x2F;a&gt;) straight to your email. The problem it solves: digital post is “push” in theory but pull in practice — you still have to log in with MitID, open an app, and click around to read a letter from SKAT or your kommune. Nemboks turns that into an email that lands in the inbox you already live in, attachments and all. Built for Danish SMBs where the same &lt;code&gt;virksomhed&lt;&#x2F;code&gt; receives post for multiple companies or employees, and where accountants, bookkeepers, and property managers want digital post in the same place as every other email. Free beta; after beta, 39 kr&#x2F;md on annual billing, 49 kr&#x2F;md monthly.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-problem-with-danish-digital-post&quot;&gt;The problem with Danish digital post&lt;&#x2F;h2&gt;
&lt;p&gt;Since 2014, communication from the Danish public sector is digital by default. That means if your kommune, SKAT, Udbetaling Danmark, or FerieKonto needs to reach you, they drop a letter into either &lt;strong&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;mit.dk&quot;&gt;mit.dk&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt; (the public portal) or &lt;strong&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.e-boks.com&#x2F;&quot;&gt;e-Boks&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt; (historically the most used private portal, now share-gated with Digital Post).&lt;&#x2F;p&gt;
&lt;p&gt;In principle this is great: centralised, auditable, no paper. In practice it’s friction-heavy:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;You need MitID to log in every time.&lt;&#x2F;strong&gt; Every tax letter, parking ticket, or vacation-pay notice is gated behind an MFA flow.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;The apps notify you, but the notifications are thin.&lt;&#x2F;strong&gt; “You have new post” — not a subject line, not a sender, not enough to triage.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;There’s no native forwarding.&lt;&#x2F;strong&gt; You can’t say “send everything from SKAT to my accountant at &lt;code&gt;bogholder@firma.dk&lt;&#x2F;code&gt;.”&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Businesses inherit the worst of both worlds.&lt;&#x2F;strong&gt; A CVR-registered company has its own mit.dk &#x2F; e-Boks inbox. If the owner doesn’t check it, nobody does.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Email, by contrast, is the default inbox for small businesses: shared mailboxes, forwarding rules, archiving, filters, search. Nemboks bridges the two.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-nemboks-does&quot;&gt;What Nemboks does&lt;&#x2F;h2&gt;
&lt;p&gt;Once set up, Nemboks authenticates against your mit.dk &#x2F; e-Boks on your behalf, polls your inbox for new post, and forwards each new letter — &lt;strong&gt;as a real email with the PDF attached&lt;&#x2F;strong&gt; — to the address(es) you configure.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;One email per letter.&lt;&#x2F;strong&gt; Subject = sender + subject. Body = plain-text preview. Attachment = the PDF.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Multiple companies per account.&lt;&#x2F;strong&gt; If you’re a revisor managing post for many CVRs, they all live under one Nemboks dashboard.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Multiple destinations per company.&lt;&#x2F;strong&gt; Forward everything from SKAT to the bogholder, everything else to the owner, for example.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Keeps the original in mit.dk &#x2F; e-Boks.&lt;&#x2F;strong&gt; Nothing is deleted — Nemboks only reads.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Logs every forward.&lt;&#x2F;strong&gt; For audit and peace of mind.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The practical outcome: the accountant or bookkeeper who already lives in Outlook or Gmail stops context-switching into a portal once a week.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;who-it-s-for&quot;&gt;Who it’s for&lt;&#x2F;h2&gt;
&lt;p&gt;Nemboks is built for small- and medium-sized Danish businesses, especially:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Accountants and bookkeepers (&lt;code&gt;revisorer&lt;&#x2F;code&gt;, &lt;code&gt;bogholdere&lt;&#x2F;code&gt;).&lt;&#x2F;strong&gt; Clients give them Nemboks access once; from then on, every SKAT letter for every client turns into an email in the shared inbox.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Property managers (&lt;code&gt;ejendomsadministratorer&lt;&#x2F;code&gt;).&lt;&#x2F;strong&gt; A single administrator might manage post for dozens of property-owning A&#x2F;S and ApS.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Small-company owners.&lt;&#x2F;strong&gt; The &lt;code&gt;enkeltmandsvirksomhed&lt;&#x2F;code&gt; or two-person &lt;code&gt;ApS&lt;&#x2F;code&gt; whose owner would rather receive a letter from their municipality in the same place as their customer email.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;the-stack&quot;&gt;The stack&lt;&#x2F;h2&gt;
&lt;p&gt;For the curious:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Rails 8&lt;&#x2F;strong&gt; on Ruby 3.4, using Hotwire (Turbo + Stimulus) and Tailwind for the dashboard.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;PostgreSQL&lt;&#x2F;strong&gt; for persistence.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;auth0.com&#x2F;&quot;&gt;Auth0&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt; for authentication. Users sign in with Google, Microsoft, or email+password — no separate Nemboks password to forget.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;stripe.com&#x2F;&quot;&gt;Stripe&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt; for subscription management, including the hosted customer portal for self-service card updates and cancellations.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;postmarkapp.com&#x2F;&quot;&gt;Postmark&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt; for transactional email — it’s the category leader for “email that must land in the inbox,” which is the entire point of a forwarding service.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Docker + Kamal&lt;&#x2F;strong&gt; for deployment. Zero-downtime rollouts on a small fleet; no Kubernetes to operate.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Cloudflare Pages&lt;&#x2F;strong&gt; for the static marketing site at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;nemboks.dk&quot;&gt;nemboks.dk&lt;&#x2F;a&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The split — Rails app on Kamal, marketing site on Pages — is deliberate. The marketing site needs to be cheap, fast, and heavily cached; the Rails app needs a database and background jobs. Running them as two separate deployments keeps each one simple.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;pricing&quot;&gt;Pricing&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Free beta&lt;&#x2F;strong&gt; while the service is stabilising.&lt;&#x2F;li&gt;
&lt;li&gt;After beta: &lt;strong&gt;39 kr&#x2F;md&lt;&#x2F;strong&gt; on annual billing, &lt;strong&gt;49 kr&#x2F;md&lt;&#x2F;strong&gt; monthly — VAT not included. Flat per-company pricing; no per-letter fees.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;60-day free trial&lt;&#x2F;strong&gt; for new customers after beta ends.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;faq&quot;&gt;FAQ&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;is-nemboks-allowed-to-read-my-mit-dk-e-boks&quot;&gt;Is Nemboks allowed to read my mit.dk &#x2F; e-Boks?&lt;&#x2F;h3&gt;
&lt;p&gt;Yes — as the inbox owner, you authorise Nemboks to read on your behalf. Nemboks only reads; it does not delete, reply, or mark as read anything you didn’t configure.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;is-this-gdpr-compliant&quot;&gt;Is this GDPR-compliant?&lt;&#x2F;h3&gt;
&lt;p&gt;Nemboks processes personal data on your behalf. As a customer you’re the data controller; Nemboks is the data processor, and there’s a standard databehandleraftale (DPA) to sign. All data is stored in the EU.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-happens-to-the-original-letter-in-mit-dk-e-boks&quot;&gt;What happens to the original letter in mit.dk &#x2F; e-Boks?&lt;&#x2F;h3&gt;
&lt;p&gt;It stays there. Nemboks is read-only.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;can-i-forward-to-multiple-email-addresses&quot;&gt;Can I forward to multiple email addresses?&lt;&#x2F;h3&gt;
&lt;p&gt;Yes. Each company can have multiple forwarding destinations, and you can route different senders to different addresses (for example, everything from SKAT to the bookkeeper).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;does-nemboks-work-for-private-individuals&quot;&gt;Does Nemboks work for private individuals?&lt;&#x2F;h3&gt;
&lt;p&gt;The current focus is Danish businesses (CVR). A private-individual plan may come later.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-if-nemboks-goes-down&quot;&gt;What if Nemboks goes down?&lt;&#x2F;h3&gt;
&lt;p&gt;Your post still arrives in mit.dk &#x2F; e-Boks normally; Nemboks just won’t forward it until the service is back. Nothing is lost.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;how-do-i-cancel&quot;&gt;How do I cancel?&lt;&#x2F;h3&gt;
&lt;p&gt;Through the Stripe customer portal from inside the dashboard. No email, no phone call.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-build-it&quot;&gt;Why build it&lt;&#x2F;h2&gt;
&lt;p&gt;Digital post works. It’s secure, it’s auditable, and I’d rather trust a SKAT letter in mit.dk than a brown envelope. But the user interface is designed for a citizen reading a handful of letters a year, not for a business receiving dozens a week across several CVRs. Email solves the “dozens a week across several mailboxes” problem well — filters, rules, shared inboxes, search — so the smallest useful bridge is to get the letters out of the portal and into email, with the PDF attached, without anyone having to log in every time.&lt;&#x2F;p&gt;
&lt;p&gt;That’s Nemboks.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>How to play Spotify on a Logitech Squeezebox in 2026</title>
        <published>2026-04-22T00:00:00+00:00</published>
        <updated>2026-04-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.x2q.net/post/squeezebox-spotify-guide/"/>
        <id>https://www.x2q.net/post/squeezebox-spotify-guide/</id>
        
        <content type="html" xml:base="https://www.x2q.net/post/squeezebox-spotify-guide/">&lt;p&gt;&lt;strong&gt;TL;DR —&lt;&#x2F;strong&gt; The official Spotify plugin Logitech shipped with Squeezebox stopped working when Spotify retired the &lt;code&gt;libspotify&lt;&#x2F;code&gt; API in 2022. In 2026 there are two working paths to get &lt;strong&gt;Spotify on a Squeezebox Classic, Touch, Boom, Radio, or Transporter&lt;&#x2F;strong&gt;: (1) &lt;strong&gt;Logitech Media Server + the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;michaelherger&#x2F;Spotty-Plugin&quot;&gt;Spotty plugin&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt; (now renamed &lt;code&gt;Spotty&lt;&#x2F;code&gt; &#x2F; sometimes &lt;code&gt;Spotty-XL&lt;&#x2F;code&gt;), which uses the Spotify Web API plus &lt;code&gt;librespot&lt;&#x2F;code&gt; under the hood, or (2) run &lt;strong&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;Spotifyd&#x2F;spotifyd&quot;&gt;spotifyd&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt; on the same host as LMS and point LMS at it as a generic audio source. Path 1 is the one you want unless you have a reason otherwise. This post walks through it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-happened-to-the-official-plugin&quot;&gt;What happened to the official plugin&lt;&#x2F;h2&gt;
&lt;p&gt;Squeezebox hardware went end-of-life at Logitech in 2012, but the software — &lt;strong&gt;Logitech Media Server (LMS)&lt;&#x2F;strong&gt;, the thing that actually streams music to the devices — has been community-maintained ever since. For years it shipped an official “Spotify” plugin that used Spotify’s now-discontinued &lt;code&gt;libspotify&lt;&#x2F;code&gt; C library. When Spotify shut &lt;code&gt;libspotify&lt;&#x2F;code&gt; down in 2022, that plugin stopped working, and Spotify has not shipped a replacement for third-party embedded devices.&lt;&#x2F;p&gt;
&lt;p&gt;The community filled the gap:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;librespot&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — an open-source, Rust reimplementation of the Spotify Connect client protocol. It’s the basis of most modern third-party Spotify integrations.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Spotty plugin for LMS&lt;&#x2F;strong&gt; — wraps &lt;code&gt;librespot&lt;&#x2F;code&gt; plus the Spotify Web API into an LMS plugin. Presents Spotify browsing, search, playlists, and playback inside the Squeezebox UI (both the device screen and the LMS web UI).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;spotifyd&lt;&#x2F;strong&gt; — a standalone &lt;code&gt;librespot&lt;&#x2F;code&gt;-based daemon that shows up as a Spotify Connect target. Your phone sees it as a speaker, you cast to it, and its audio output goes somewhere LMS can pick it up.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;path-1-recommended-spotty-plugin-on-lms&quot;&gt;Path 1 (recommended): Spotty plugin on LMS&lt;&#x2F;h2&gt;
&lt;p&gt;This is the cleanest experience and the one that feels native on the Squeezebox. You search and browse from the Squeezebox remote or Touch screen, and it Just Works.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;1-get-lms-running&quot;&gt;1. Get LMS running&lt;&#x2F;h3&gt;
&lt;p&gt;If you’re not already running LMS, install it on whatever always-on machine you have — a Raspberry Pi, a NAS with a Docker runtime, or a small Linux box is ideal.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;# Debian&#x2F;Ubuntu — grab the latest .deb from the community repo
wget https:&#x2F;&#x2F;downloads.slimdevices.com&#x2F;LogitechMediaServer_v8.x&#x2F;logitechmediaserver_&amp;lt;latest&amp;gt;_amd64.deb
sudo apt install .&#x2F;logitechmediaserver_&amp;lt;latest&amp;gt;_amd64.deb
sudo systemctl enable --now logitechmediaserver
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Or via Docker — &lt;code&gt;lmscommunity&#x2F;logitechmediaserver&lt;&#x2F;code&gt; is a maintained image:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;docker run -d --name lms \
  --network host \
  -v lms-config:&#x2F;config \
  -v &#x2F;path&#x2F;to&#x2F;music:&#x2F;music:ro \
  lmscommunity&#x2F;logitechmediaserver:latest
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Open &lt;code&gt;http:&#x2F;&#x2F;&amp;lt;host&amp;gt;:9000&#x2F;&lt;&#x2F;code&gt; and you should see the LMS web UI. Your Squeezebox devices on the same LAN should appear under &lt;strong&gt;Players&lt;&#x2F;strong&gt; automatically.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;2-install-spotty&quot;&gt;2. Install Spotty&lt;&#x2F;h3&gt;
&lt;p&gt;In the LMS web UI: &lt;strong&gt;Settings → Plugins → Install new plugins&lt;&#x2F;strong&gt; and tick &lt;strong&gt;Spotty&lt;&#x2F;strong&gt; (author: Michael Herger). LMS downloads, installs, and restarts. On restart, you’ll have a &lt;strong&gt;Spotty&lt;&#x2F;strong&gt; entry in &lt;strong&gt;Settings → Advanced → Spotty&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;3-authorise-spotty-with-your-spotify-account&quot;&gt;3. Authorise Spotty with your Spotify account&lt;&#x2F;h3&gt;
&lt;ul&gt;
&lt;li&gt;In &lt;strong&gt;Settings → Advanced → Spotty&lt;&#x2F;strong&gt;, click &lt;strong&gt;Add account&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;Spotty kicks off an OAuth flow via Spotify’s Web API — you log in once in a browser, approve the access, and the token is stored on the LMS server.&lt;&#x2F;li&gt;
&lt;li&gt;You need &lt;strong&gt;Spotify Premium&lt;&#x2F;strong&gt; for playback. (This is a Spotify-side limit: &lt;code&gt;librespot&lt;&#x2F;code&gt; can authenticate with Free, but full-quality playback is Premium-only.)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;4-play-something&quot;&gt;4. Play something&lt;&#x2F;h3&gt;
&lt;p&gt;On the Squeezebox device itself, &lt;strong&gt;Home → My Music → Spotty&lt;&#x2F;strong&gt; (or &lt;strong&gt;Home → Music Library → Spotty&lt;&#x2F;strong&gt;, depending on your LMS version) — browse playlists, search, start a track. On the Squeezebox Touch the on-screen artwork works. On the Classic the text UI works. On the Radio and Boom you drive it from the web UI or the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;squeeze-commander.com&#x2F;&quot;&gt;Squeeze Commander&lt;&#x2F;a&gt; &#x2F; &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;play.google.com&#x2F;store&#x2F;apps&#x2F;details?id=uk.org.ngo.squeezer&quot;&gt;Squeezer&lt;&#x2F;a&gt; apps.&lt;&#x2F;p&gt;
&lt;p&gt;Spotty also adds Squeezebox devices as &lt;strong&gt;Spotify Connect targets&lt;&#x2F;strong&gt; — meaning you can cast from your phone’s Spotify app to “Living Room Squeezebox” natively, the same way you’d cast to a Sonos or a Google Home.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;path-2-spotifyd-as-a-connect-bridge&quot;&gt;Path 2: spotifyd as a Connect bridge&lt;&#x2F;h2&gt;
&lt;p&gt;Use this if Spotty won’t install (old LMS version, old Perl, weird OS) or you specifically want Spotify Connect behaviour without Spotty.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;# Debian&#x2F;Ubuntu
sudo apt install spotifyd
# or from source: cargo install spotifyd
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Configure &lt;code&gt;&#x2F;etc&#x2F;spotifyd.conf&lt;&#x2F;code&gt; with your Spotify credentials, a backend (&lt;code&gt;alsa&lt;&#x2F;code&gt;, &lt;code&gt;pulseaudio&lt;&#x2F;code&gt;, or &lt;code&gt;pipe&lt;&#x2F;code&gt;), and a device name. Start the daemon:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl enable --now spotifyd
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;From your phone, cast to the &lt;code&gt;spotifyd&lt;&#x2F;code&gt;-named device. Audio comes out of the host.&lt;&#x2F;p&gt;
&lt;p&gt;To get that audio into a Squeezebox, pipe &lt;code&gt;spotifyd&lt;&#x2F;code&gt; to a FIFO and play the FIFO in LMS:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;# spotifyd.conf
backend = &amp;quot;pipe&amp;quot;
device = &amp;quot;&#x2F;var&#x2F;run&#x2F;spotifyd.pipe&amp;quot;
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then in LMS: add the FIFO as a &lt;strong&gt;File system music folder&lt;&#x2F;strong&gt; and play it as a live stream.&lt;&#x2F;p&gt;
&lt;p&gt;This is clunkier and has ~1 s of latency vs. Spotty’s tight integration. Only use it if Path 1 doesn’t work for you.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;works-on-which-squeezebox&quot;&gt;Works on which Squeezebox?&lt;&#x2F;h2&gt;
&lt;p&gt;Spotty works on every Squeezebox device that talks to LMS:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Squeezebox Classic &#x2F; Squeezebox v3&lt;&#x2F;strong&gt; (text display) — text-UI browsing and playback.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Squeezebox Touch&lt;&#x2F;strong&gt; — colour touchscreen, artwork, the whole UI.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Squeezebox Boom&lt;&#x2F;strong&gt; — text UI, same as Classic.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Squeezebox Radio&lt;&#x2F;strong&gt; — text UI, built-in speaker.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Squeezebox Duet &#x2F; Controller&lt;&#x2F;strong&gt; — the controller’s UI works; playback goes to the Receiver.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Transporter&lt;&#x2F;strong&gt; — full support.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;piCorePlayer &#x2F; SqueezeLite on Pi&lt;&#x2F;strong&gt; — yes, these are software Squeezeboxes and Spotty streams to them fine.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;As long as the device can connect to LMS, Spotty can feed it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;faq&quot;&gt;FAQ&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;do-i-need-spotify-premium&quot;&gt;Do I need Spotify Premium?&lt;&#x2F;h3&gt;
&lt;p&gt;Yes, for playback. &lt;code&gt;librespot&lt;&#x2F;code&gt; (and therefore Spotty) requires a Premium account to decode audio. Spotify Free accounts can authenticate but won’t play.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;does-spotify-connect-appear-the-squeezebox-as-a-speaker-work&quot;&gt;Does Spotify Connect “appear the Squeezebox as a speaker” work?&lt;&#x2F;h3&gt;
&lt;p&gt;Yes, with Spotty installed. Each Squeezebox player shows up in the Spotify app’s device picker as a Connect target.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;does-hi-res-lossless-work&quot;&gt;Does hi-res &#x2F; lossless work?&lt;&#x2F;h3&gt;
&lt;p&gt;Spotify itself doesn’t offer lossless on standard plans (Hi-Fi has been “coming soon” for years). Spotty plays whatever Spotify serves — Ogg Vorbis 320 kbps on Premium — and the Squeezebox handles it natively.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;gapless-playback&quot;&gt;Gapless playback?&lt;&#x2F;h3&gt;
&lt;p&gt;Yes, Spotty supports gapless.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;does-this-break-if-spotify-s-web-api-changes&quot;&gt;Does this break if Spotify’s Web API changes?&lt;&#x2F;h3&gt;
&lt;p&gt;Yes, occasionally — Spotify periodically rotates OAuth scopes or deprecates endpoints, and Spotty gets a patch release. Keep the plugin updated.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;can-i-run-lms-on-the-same-box-as-my-nas&quot;&gt;Can I run LMS on the same box as my NAS?&lt;&#x2F;h3&gt;
&lt;p&gt;Absolutely. LMS is lightweight (~200 MB RAM, minimal CPU except during library scans). Running it next to Samba or on a homelab SBC is the common setup.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;is-this-the-same-as-picoreplayer&quot;&gt;Is this the same as “piCorePlayer”?&lt;&#x2F;h3&gt;
&lt;p&gt;piCorePlayer is a tiny Linux distribution that turns a Raspberry Pi into a Squeezebox-compatible player (via SqueezeLite). It does not replace LMS — you still need LMS as the server. piCorePlayer + LMS + Spotty is a very common 2026 stack for people whose original Squeezebox hardware finally died.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-to-do-if-your-squeezebox-won-t-connect-to-lms-at-all&quot;&gt;What to do if your Squeezebox won’t connect to LMS at all&lt;&#x2F;h2&gt;
&lt;p&gt;Two things to check first:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;mDNS &#x2F; multicast across your network.&lt;&#x2F;strong&gt; Modern Wi-Fi routers often block multicast between SSIDs or to wired hosts. Put the Squeezebox and LMS on the same subnet&#x2F;VLAN with multicast allowed.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;SlimProto port (3483&#x2F;udp and 3483&#x2F;tcp).&lt;&#x2F;strong&gt; That’s what the Squeezebox uses to find LMS. Not blocked by anything sensible, but worth checking if you run a strict firewall.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Beyond that, the Squeezebox community is still active — &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;forums.slimdevices.com&#x2F;&quot;&gt;forums.slimdevices.com&lt;&#x2F;a&gt; and the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LMS-Community&quot;&gt;LMS Community GitHub&lt;&#x2F;a&gt; are where problems get solved.&lt;&#x2F;p&gt;
&lt;p&gt;Squeezebox hardware was discontinued fifteen years ago. Thanks to LMS, Spotty, and &lt;code&gt;librespot&lt;&#x2F;code&gt;, it still plays Spotify better than most “smart speakers” sold new in 2026.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>elpriser.org — Danish hourly electricity prices</title>
        <published>2026-04-20T00:00:00+00:00</published>
        <updated>2026-04-20T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.x2q.net/post/elpriser-org-danish-electricity-prices/"/>
        <id>https://www.x2q.net/post/elpriser-org-danish-electricity-prices/</id>
        
        <content type="html" xml:base="https://www.x2q.net/post/elpriser-org-danish-electricity-prices/">&lt;p&gt;&lt;strong&gt;TL;DR —&lt;&#x2F;strong&gt; &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;elpriser.org&quot;&gt;elpriser.org&lt;&#x2F;a&gt; shows the &lt;strong&gt;real, all-in hourly price of electricity in Denmark&lt;&#x2F;strong&gt; — not just the spot price. It combines the Nord Pool spot price, your local grid tariff (&lt;code&gt;netselskab&lt;&#x2F;code&gt;), Energinet’s system and transmission tariffs, the state electricity tax (&lt;code&gt;elafgift&lt;&#x2F;code&gt;), and 25% VAT. Data comes from &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.energidataservice.dk&#x2F;&quot;&gt;Energi Data Service&lt;&#x2F;a&gt; and updates daily after Nord Pool publishes the next 24 hours around 13:00. DK1 (Vestdanmark) and DK2 (Østdanmark). Danish-language, free, no login.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-does-danish-electricity-actually-cost&quot;&gt;What does Danish electricity actually cost?&lt;&#x2F;h2&gt;
&lt;p&gt;The “spot price” you see on most energy dashboards is only one component. The price you actually pay per kWh is made up of six parts:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Spot price&lt;&#x2F;strong&gt; — set hourly on the Nord Pool wholesale market for the following day.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Grid tariff (nettarif)&lt;&#x2F;strong&gt; — paid to your local grid operator (&lt;code&gt;netselskab&lt;&#x2F;code&gt;). Varies by company and by time-of-day (off-peak, shoulder, peak).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;System tariff (systemtarif)&lt;&#x2F;strong&gt; — paid to Energinet, the Danish TSO.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Transmission tariff (transmissionstarif)&lt;&#x2F;strong&gt; — also paid to Energinet.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Electricity tax (elafgift)&lt;&#x2F;strong&gt; — a state tax on consumption.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;VAT (moms)&lt;&#x2F;strong&gt; — 25% applied on top of everything else.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Most price sites show the first number. A few show one or two more. None that I could find showed all six, hour by hour, for both price zones, with the correct grid tariff automatically picked for the user’s &lt;code&gt;netselskab&lt;&#x2F;code&gt;. That’s what elpriser.org does.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-two-price-zones-dk1-and-dk2&quot;&gt;The two price zones: DK1 and DK2&lt;&#x2F;h2&gt;
&lt;p&gt;Denmark is split into two electricity price zones by geography — the Great Belt separates them — and they often have different prices at the same hour:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DK1 — Vestdanmark.&lt;&#x2F;strong&gt; Jylland and Fyn. More wind-dominated.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;DK2 — Østdanmark.&lt;&#x2F;strong&gt; Sjælland, Lolland, Falster, Møn, Bornholm. More interconnected with Sweden (SE4) and Germany.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;When the wind blows hard in Jylland, DK1 can be cheap while DK2 is pricey. When an interconnector is down, the gap widens further. elpriser.org shows both zones side by side.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;which-netselskab-are-you-on&quot;&gt;Which netselskab are you on?&lt;&#x2F;h2&gt;
&lt;p&gt;You cannot choose your &lt;code&gt;netselskab&lt;&#x2F;code&gt; — it depends on where you live — but the grid tariff they charge is a significant chunk of your bill, and it varies by &lt;strong&gt;15–25 øre&#x2F;kWh at peak&lt;&#x2F;strong&gt; between the cheapest and the most expensive, which works out to &lt;strong&gt;~500–1,000 kr&#x2F;year&lt;&#x2F;strong&gt; for a typical household.&lt;&#x2F;p&gt;
&lt;p&gt;Rough ranking of peak (spidslast) tariffs as of 2025:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Zone&lt;&#x2F;th&gt;&lt;th&gt;Netselskab&lt;&#x2F;th&gt;&lt;th&gt;Peak tariff (øre&#x2F;kWh)&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;DK1&lt;&#x2F;td&gt;&lt;td&gt;RAH Net&lt;&#x2F;td&gt;&lt;td&gt;~33&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;DK1&lt;&#x2F;td&gt;&lt;td&gt;Trefor&lt;&#x2F;td&gt;&lt;td&gt;~37&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;DK1&lt;&#x2F;td&gt;&lt;td&gt;N1&lt;&#x2F;td&gt;&lt;td&gt;~46&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;DK2&lt;&#x2F;td&gt;&lt;td&gt;Cerius&lt;&#x2F;td&gt;&lt;td&gt;~47&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;DK2&lt;&#x2F;td&gt;&lt;td&gt;Radius&lt;&#x2F;td&gt;&lt;td&gt;~65&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Smaller operators (Konstant, Nord Energi, Vores Elnet, El-net Kongerslev, and others) each have their own tariffs. elpriser.org lets you pick yours; the all-in hourly price adjusts accordingly.&lt;&#x2F;p&gt;
&lt;p&gt;You can’t switch &lt;code&gt;netselskab&lt;&#x2F;code&gt;, but you &lt;strong&gt;can&lt;&#x2F;strong&gt; shift consumption — laundry, dishwasher, EV charging, heat-pump heating — into the low-tariff hours. The hourly chart exists for exactly that.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;historical-extremes&quot;&gt;Historical extremes&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Record high:&lt;&#x2F;strong&gt; 16.69 kr&#x2F;kWh (spot price, excl. VAT) in DK2, at 19:00 on 5 September 2022, during the European energy crisis.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Record low:&lt;&#x2F;strong&gt; −2.76 kr&#x2F;kWh in DK1, at 14:00 on 2 July 2023, when wind production overshot demand.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Negative prices are not a bug: when production is high and demand is low, producers pay consumers to absorb the surplus. Modern price-aware heat pumps and EV chargers can lean into this.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-elpriser-org-is-built&quot;&gt;How elpriser.org is built&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Static HTML&lt;&#x2F;strong&gt;, regenerated once per day after Nord Pool’s next-day publication around 13:00 CET.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Data source&lt;&#x2F;strong&gt;: &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.energidataservice.dk&#x2F;&quot;&gt;Energi Data Service&lt;&#x2F;a&gt;, the open-data service from Energinet. No API keys required.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Structured data&lt;&#x2F;strong&gt;: &lt;code&gt;schema.org&#x2F;WebApplication&lt;&#x2F;code&gt; and &lt;code&gt;FAQPage&lt;&#x2F;code&gt; are emitted on every page so Google can surface the prices directly in search results and AI overviews.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Hosting&lt;&#x2F;strong&gt;: a small origin behind Cloudflare. Cacheable for the full day; purged on the daily rebuild.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Language&lt;&#x2F;strong&gt;: Danish only. The domain name should have been a hint.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The whole thing is a single-purpose tool: no tracking, no login, no newsletter popover, no “10 ways to save on your electricity bill” listicles.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;faq&quot;&gt;FAQ&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;how-often-does-elpriser-org-update&quot;&gt;How often does elpriser.org update?&lt;&#x2F;h3&gt;
&lt;p&gt;Once a day, after the Nord Pool day-ahead auction publishes next-day prices around 13:00 CET. Intraday adjustments are not shown; most consumers are billed against the day-ahead price anyway.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;is-the-price-shown-with-or-without-vat&quot;&gt;Is the price shown with or without VAT?&lt;&#x2F;h3&gt;
&lt;p&gt;With. The headline number on elpriser.org is the all-in price per kWh: spot + tariffs + tax + 25% VAT. You can toggle to see the breakdown.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;why-are-prices-in-dk1-and-dk2-different&quot;&gt;Why are prices in DK1 and DK2 different?&lt;&#x2F;h3&gt;
&lt;p&gt;Denmark has two physically separated electricity areas joined by the Great Belt interconnector. When the interconnector is fully used or wind production differs sharply between east and west, the two zones clear at different Nord Pool prices.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;can-i-see-the-breakdown-of-each-component&quot;&gt;Can I see the breakdown of each component?&lt;&#x2F;h3&gt;
&lt;p&gt;Yes. Every hourly cell can be expanded to show spot price, each tariff component, elafgift, and VAT.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;do-i-really-pay-negative-prices-when-the-wholesale-price-goes-negative&quot;&gt;Do I really pay negative prices when the wholesale price goes negative?&lt;&#x2F;h3&gt;
&lt;p&gt;It depends on your contract. Pure spot-price contracts pass the raw Nord Pool price through — tariffs and tax still apply, so the all-in price can go slightly negative or just very low. Fixed-price contracts don’t.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;why-danish-only&quot;&gt;Why Danish only?&lt;&#x2F;h3&gt;
&lt;p&gt;The data, the regulatory rules, the &lt;code&gt;netselskab&lt;&#x2F;code&gt; structure, and the target audience are all Danish. An English version would be translating context, not content.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;where-does-the-data-come-from&quot;&gt;Where does the data come from?&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.energidataservice.dk&#x2F;en&#x2F;dataset&#x2F;elspotprices&quot;&gt;Energi Data Service&lt;&#x2F;a&gt; (Nord Pool spot) and Energinet’s tariff data. Both are official public data sets from the Danish TSO.&lt;&#x2F;p&gt;
&lt;p&gt;Small, focused, no login, no tracking. The way consumer energy tools should look.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>winniemethmann.com — from WordPress to Astro</title>
        <published>2026-04-19T00:00:00+00:00</published>
        <updated>2026-04-19T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.x2q.net/post/winniemethmann-com-astro-portfolio/"/>
        <id>https://www.x2q.net/post/winniemethmann-com-astro-portfolio/</id>
        
        <content type="html" xml:base="https://www.x2q.net/post/winniemethmann-com-astro-portfolio/">&lt;p&gt;&lt;strong&gt;TL;DR —&lt;&#x2F;strong&gt; &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;winniemethmann.com&quot;&gt;winniemethmann.com&lt;&#x2F;a&gt; is the portfolio of a Danish food photographer and recipe developer. It was a WordPress site for a decade. I rebuilt it on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;astro.build&quot;&gt;Astro&lt;&#x2F;a&gt;, using &lt;strong&gt;content collections&lt;&#x2F;strong&gt; (typed Markdown) instead of a CMS, &lt;strong&gt;Sharp + AVIF&lt;&#x2F;strong&gt; for the image pipeline, and &lt;strong&gt;Astro’s i18n routing&lt;&#x2F;strong&gt; (Danish default, English at &lt;code&gt;&#x2F;en&#x2F;&lt;&#x2F;code&gt;). Build time: ~30 seconds. Output size: ~70% smaller. No more admin panel, no more plugin updates, no more bot-probed login endpoint.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-move-off-wordpress&quot;&gt;Why move off WordPress&lt;&#x2F;h2&gt;
&lt;p&gt;For a portfolio that changes a few times a month, WordPress was doing too much work:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PHP runtime and MySQL&lt;&#x2F;strong&gt; for a site that is functionally static.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Dozens of plugins&lt;&#x2F;strong&gt; for image galleries, contact forms, SEO, caching — each with its own update cadence and security disclosures.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;A half-forked theme&lt;&#x2F;strong&gt; that had accumulated patches nobody remembered why.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;An admin login endpoint&lt;&#x2F;strong&gt; that was probed several thousand times a day by bots.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Regular caching headaches&lt;&#x2F;strong&gt; every time the CDN and the plugins disagreed.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Concretely, dropping WordPress meant:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No admin panel to keep patched.&lt;&#x2F;strong&gt; WordPress core releases, plugin updates, theme updates — gone.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;No database.&lt;&#x2F;strong&gt; Content lives as Markdown in the repo.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;No login surface for bots to probe.&lt;&#x2F;strong&gt; There’s no &lt;code&gt;&#x2F;wp-admin&#x2F;&lt;&#x2F;code&gt; anymore.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;~10× faster page loads&lt;&#x2F;strong&gt; with no caching layer required.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;~70% smaller total build size&lt;&#x2F;strong&gt;, swapping hand-exported JPEGs for AVIF at sensible &lt;code&gt;srcset&lt;&#x2F;code&gt; breakpoints.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The tradeoff is that non-technical editing goes away. In practice that did not matter: the site owner was happier editing Markdown than fighting the WordPress block editor.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-astro&quot;&gt;Why Astro&lt;&#x2F;h2&gt;
&lt;p&gt;I considered Hugo, 11ty, Next.js static export, and SvelteKit. Astro won on three specific points:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Content collections.&lt;&#x2F;strong&gt; A typed, schema-checked way to describe a portfolio project as a directory of photos plus some front-matter. Build fails loudly if anything is malformed. No CMS required.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;The &lt;code&gt;&amp;lt;Image&amp;gt;&lt;&#x2F;code&gt; component.&lt;&#x2F;strong&gt; Astro’s built-in image pipeline handles AVIF + JPEG fallback + &lt;code&gt;srcset&lt;&#x2F;code&gt; + &lt;code&gt;width&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;height&lt;&#x2F;code&gt; attributes with a one-liner. Sharp is the engine under the hood.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Islands architecture, not relevant here.&lt;&#x2F;strong&gt; The site has no interactive components, so it ships basically zero JavaScript.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;content-collections-not-a-cms&quot;&gt;Content collections, not a CMS&lt;&#x2F;h2&gt;
&lt;p&gt;Each portfolio project is a directory under &lt;code&gt;src&#x2F;content&#x2F;portfolio&#x2F;&lt;&#x2F;code&gt; with a front-matter schema:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;src&#x2F;content&#x2F;portfolio&#x2F;
├── 2024-cookbook-editorial&#x2F;
│   ├── index.mdx
│   ├── cover.jpg
│   ├── 01.jpg, 02.jpg, …
└── 2023-spring-catalogue&#x2F;
    ├── index.mdx
    ├── cover.jpg
    └── 01.jpg …
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;index.mdx&lt;&#x2F;code&gt; front-matter declares the project title, category, year, and cover image. Astro enforces the schema at build time via &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.astro.build&#x2F;en&#x2F;guides&#x2F;content-collections&#x2F;&quot;&gt;&lt;code&gt;defineCollection&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; with a Zod schema. If a project is missing a cover image or has a bad category, the build fails.&lt;&#x2F;p&gt;
&lt;p&gt;Categories used: &lt;strong&gt;food photography, recipe development, interior &amp;amp; garden styling, editorial, cookbooks, and fashion&lt;&#x2F;strong&gt;. Adding a new project is &lt;code&gt;mkdir&lt;&#x2F;code&gt; + &lt;code&gt;cp *.jpg&lt;&#x2F;code&gt; + a short YAML front-matter block. No admin UI, no database migration, no cache to invalidate.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;image-pipeline-sharp-avif&quot;&gt;Image pipeline: Sharp + AVIF&lt;&#x2F;h2&gt;
&lt;p&gt;Food photography lives or dies on image quality. Astro’s &lt;code&gt;&amp;lt;Image&amp;gt;&lt;&#x2F;code&gt; component, powered by &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;sharp.pixelplumbing.com&#x2F;&quot;&gt;Sharp&lt;&#x2F;a&gt;, generates:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AVIF&lt;&#x2F;strong&gt; as the primary format. Roughly all modern browsers support it now (see &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;caniuse.com&#x2F;avif&quot;&gt;caniuse&lt;&#x2F;a&gt;).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;JPEG&lt;&#x2F;strong&gt; fallback with matching &lt;code&gt;srcset&lt;&#x2F;code&gt; breakpoints (320, 640, 960, 1280, 1920 px).&lt;&#x2F;li&gt;
&lt;li&gt;Explicit &lt;code&gt;width&lt;&#x2F;code&gt; and &lt;code&gt;height&lt;&#x2F;code&gt; attributes, so there is zero cumulative layout shift while images load.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;loading=&quot;lazy&quot;&lt;&#x2F;code&gt; for below-the-fold images and &lt;code&gt;fetchpriority=&quot;high&quot;&lt;&#x2F;code&gt; for the hero.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;strong&gt;Numbers&lt;&#x2F;strong&gt;: a typical portfolio page went from ~4.8 MB of hand-exported JPEGs to ~1.4 MB of AVIF — about a 70% reduction — with no visible quality loss at typical display sizes.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;i18n-danish-default-english-at-en&quot;&gt;i18n — Danish default, English at &#x2F;en&#x2F;&lt;&#x2F;h2&gt;
&lt;p&gt;Astro’s i18n routing sits in &lt;code&gt;astro.config.mjs&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;js&quot;&gt;export default defineConfig({
  i18n: {
    defaultLocale: &amp;quot;da&amp;quot;,
    locales: [&amp;quot;da&amp;quot;, &amp;quot;en&amp;quot;],
    routing: { prefixDefaultLocale: false },
  },
});
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That gives:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&#x2F;&lt;&#x2F;code&gt; for Danish (the default, no prefix).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;&#x2F;en&#x2F;&lt;&#x2F;code&gt; for English.&lt;&#x2F;li&gt;
&lt;li&gt;Each content entry declares its language via its collection and filename.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;link rel=&quot;alternate&quot; hreflang&amp;gt;&lt;&#x2F;code&gt; and the sitemap are generated from the same source.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Posts and portfolio entries that lack a translation simply don’t appear in the other locale’s routing — they don’t 404 to a wrong-language page.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;deployment-and-build&quot;&gt;Deployment and build&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Output&lt;&#x2F;strong&gt;: fully static, deployed as plain files behind Cloudflare.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Build time&lt;&#x2F;strong&gt;: ~30 seconds on a cold cache, ~6 seconds incrementally.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;No server, no database, no runtime cost.&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;measurable-outcomes&quot;&gt;Measurable outcomes&lt;&#x2F;h2&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Metric&lt;&#x2F;th&gt;&lt;th&gt;WordPress (before)&lt;&#x2F;th&gt;&lt;th&gt;Astro (after)&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Build &#x2F; deploy time&lt;&#x2F;td&gt;&lt;td&gt;n&#x2F;a (instant publish)&lt;&#x2F;td&gt;&lt;td&gt;~30s cold, ~6s warm&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Typical page size (portfolio page)&lt;&#x2F;td&gt;&lt;td&gt;~4.8 MB&lt;&#x2F;td&gt;&lt;td&gt;~1.4 MB&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Largest Contentful Paint&lt;&#x2F;td&gt;&lt;td&gt;~2.8 s&lt;&#x2F;td&gt;&lt;td&gt;~0.9 s&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Time to Interactive&lt;&#x2F;td&gt;&lt;td&gt;~3.5 s&lt;&#x2F;td&gt;&lt;td&gt;~1.1 s&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;JS shipped&lt;&#x2F;td&gt;&lt;td&gt;~220 KB&lt;&#x2F;td&gt;&lt;td&gt;~0 KB&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Plugins to keep patched&lt;&#x2F;td&gt;&lt;td&gt;14&lt;&#x2F;td&gt;&lt;td&gt;0&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;h2 id=&quot;faq&quot;&gt;FAQ&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;can-a-non-technical-owner-still-edit-the-site&quot;&gt;Can a non-technical owner still edit the site?&lt;&#x2F;h3&gt;
&lt;p&gt;Yes, for copy. Editing Markdown in a GitHub web editor is, in practice, easier than the WordPress block editor. For new portfolio projects, the workflow is: drag images into a folder, write a short front-matter block, commit. If that’s too technical, a one-page admin (Decap CMS &#x2F; Sveltia CMS &#x2F; Keystatic) can be bolted on.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-about-seo-after-the-migration&quot;&gt;What about SEO after the migration?&lt;&#x2F;h3&gt;
&lt;p&gt;URLs were kept as-is wherever possible. Missing old paths redirect via Cloudflare rules. The sitemap is regenerated on every build, and structured data (&lt;code&gt;Person&lt;&#x2F;code&gt;, &lt;code&gt;ImageGallery&lt;&#x2F;code&gt;, &lt;code&gt;Article&lt;&#x2F;code&gt;) is emitted per page.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;why-astro-and-not-hugo&quot;&gt;Why Astro and not Hugo?&lt;&#x2F;h3&gt;
&lt;p&gt;Hugo is faster and simpler for pure blogging, but Astro’s typed content collections and first-class &lt;code&gt;&amp;lt;Image&amp;gt;&lt;&#x2F;code&gt; component won this case. For &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.x2q.net&quot;&gt;x2q.net&lt;&#x2F;a&gt; itself, I later went with Zola — for a portfolio with a heavy image pipeline, Astro remained the better fit.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;how-do-images-stay-organised&quot;&gt;How do images stay organised?&lt;&#x2F;h3&gt;
&lt;p&gt;Each portfolio project owns its own folder. The Git repo is the CMS. &lt;code&gt;git log&lt;&#x2F;code&gt; tells you when an image was added and why.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;is-the-build-deterministic&quot;&gt;Is the build deterministic?&lt;&#x2F;h3&gt;
&lt;p&gt;Yes. Given the same inputs, the output is byte-for-byte identical. Sharp is pinned in &lt;code&gt;package.json&lt;&#x2F;code&gt;; so is Astro. CI runs on Node LTS.&lt;&#x2F;p&gt;
&lt;p&gt;If you’re on a WordPress site that’s doing far less than its infrastructure suggests, Astro is worth the afternoon it takes to try.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>apextowww.com — a free apex-to-www redirect service</title>
        <published>2026-04-18T00:00:00+00:00</published>
        <updated>2026-04-18T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.x2q.net/post/apextowww-com-apex-to-www-redirect/"/>
        <id>https://www.x2q.net/post/apextowww-com-apex-to-www-redirect/</id>
        
        <content type="html" xml:base="https://www.x2q.net/post/apextowww-com-apex-to-www-redirect/">&lt;p&gt;&lt;strong&gt;TL;DR —&lt;&#x2F;strong&gt; DNS does not allow &lt;code&gt;CNAME&lt;&#x2F;code&gt; records at the zone apex (&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.rfc-editor.org&#x2F;rfc&#x2F;rfc1034#section-3.6.2&quot;&gt;RFC 1034 §3.6.2&lt;&#x2F;a&gt;). That’s why hosts like Netlify, Vercel, Cloudflare Pages, Firebase, and Heroku ask you to configure &lt;code&gt;www.example.com&lt;&#x2F;code&gt; with a &lt;code&gt;CNAME&lt;&#x2F;code&gt; and leave &lt;code&gt;example.com&lt;&#x2F;code&gt; (the apex) as a problem. &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;apextowww.com&quot;&gt;apextowww.com&lt;&#x2F;a&gt; solves it: point two &lt;code&gt;A&lt;&#x2F;code&gt; records and two &lt;code&gt;AAAA&lt;&#x2F;code&gt; records at the service, and it issues a Let’s Encrypt certificate for your apex and &lt;code&gt;301&lt;&#x2F;code&gt;-redirects every request to &lt;code&gt;https:&#x2F;&#x2F;www.yourdomain.tld&#x2F;&lt;&#x2F;code&gt;, preserving path and query string. Free, no signup, no account.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-you-can-t-put-a-cname-on-the-apex&quot;&gt;Why you can’t put a CNAME on the apex&lt;&#x2F;h2&gt;
&lt;p&gt;The DNS spec is unambiguous: a zone apex (the “bare” domain like &lt;code&gt;example.com&lt;&#x2F;code&gt;) must carry an &lt;code&gt;SOA&lt;&#x2F;code&gt; record and usually &lt;code&gt;NS&lt;&#x2F;code&gt; records. &lt;code&gt;CNAME&lt;&#x2F;code&gt; is defined as an alias that must be the only record at a name, and it is forbidden to coexist with the &lt;code&gt;SOA&lt;&#x2F;code&gt; and &lt;code&gt;NS&lt;&#x2F;code&gt; records that the apex is required to have. That’s why you can &lt;code&gt;CNAME www.example.com → mysite.netlify.app&lt;&#x2F;code&gt; but you &lt;strong&gt;cannot&lt;&#x2F;strong&gt; &lt;code&gt;CNAME example.com → mysite.netlify.app&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Workarounds exist, and all of them are a little awkward:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ALIAS &#x2F; ANAME records.&lt;&#x2F;strong&gt; Proprietary to each DNS provider (Cloudflare, Route 53, DNSimple, NS1). They work by resolving the target behind the scenes and returning an &lt;code&gt;A&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;AAAA&lt;&#x2F;code&gt;. Fine if your DNS provider supports it; useless if it doesn’t.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Flattening at the provider level.&lt;&#x2F;strong&gt; Cloudflare’s “CNAME flattening” is this, done automatically for you.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Your own always-on VPS doing &lt;code&gt;301 → www&lt;&#x2F;code&gt;.&lt;&#x2F;strong&gt; This is what I was doing for multiple domains, and it’s both over-engineered and a small maintenance tax.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;apextowww is the fourth option: somebody else’s always-on redirector, managed for you.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-apextowww-does&quot;&gt;What apextowww does&lt;&#x2F;h2&gt;
&lt;p&gt;Exactly one thing: a &lt;code&gt;301 Moved Permanently&lt;&#x2F;code&gt; from &lt;code&gt;https:&#x2F;&#x2F;yourdomain.tld&#x2F;anything?x=y&lt;&#x2F;code&gt; to &lt;code&gt;https:&#x2F;&#x2F;www.yourdomain.tld&#x2F;anything?x=y&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TLS&lt;&#x2F;strong&gt; is issued automatically on first request via the Let’s Encrypt HTTP-01 challenge. No ACME client to run yourself.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;IPv4 + IPv6&lt;&#x2F;strong&gt; on dual-stack by design. Both apex records are required.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;HTTP&#x2F;1.1, HTTP&#x2F;2, and HTTP&#x2F;3&lt;&#x2F;strong&gt; are all served. The redirect itself is trivially small, so protocol version matters less, but it helps the first-paint story when the redirect is on the critical path.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Path and query string&lt;&#x2F;strong&gt; are preserved, so deep links keep working.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;No signup, no login, no account.&lt;&#x2F;strong&gt; If your DNS is pointed correctly, it just works.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;how-to-set-it-up&quot;&gt;How to set it up&lt;&#x2F;h2&gt;
&lt;ol&gt;
&lt;li&gt;Go to &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;apextowww.com&quot;&gt;apextowww.com&lt;&#x2F;a&gt; and copy the current IP addresses (two IPv4, two IPv6).&lt;&#x2F;li&gt;
&lt;li&gt;In your DNS provider, set &lt;code&gt;A&lt;&#x2F;code&gt; records on your apex pointing at the two IPv4 addresses. Remove any existing &lt;code&gt;A&lt;&#x2F;code&gt; record at the apex.&lt;&#x2F;li&gt;
&lt;li&gt;Set &lt;code&gt;AAAA&lt;&#x2F;code&gt; records on your apex pointing at the two IPv6 addresses.&lt;&#x2F;li&gt;
&lt;li&gt;Make sure &lt;code&gt;www.yourdomain.tld&lt;&#x2F;code&gt; still points at your real host (CNAME to Netlify&#x2F;Vercel&#x2F;Pages&#x2F;etc).&lt;&#x2F;li&gt;
&lt;li&gt;Wait for DNS to propagate. Visit &lt;code&gt;http:&#x2F;&#x2F;yourdomain.tld&#x2F;&lt;&#x2F;code&gt; — it should 301 to &lt;code&gt;https:&#x2F;&#x2F;www.yourdomain.tld&#x2F;&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;The apextowww site has per-platform walkthroughs at &lt;code&gt;&#x2F;netlify-apex-domain-redirect&#x2F;&lt;&#x2F;code&gt;, &lt;code&gt;&#x2F;vercel-apex-domain-redirect&#x2F;&lt;&#x2F;code&gt;, &lt;code&gt;&#x2F;cloudflare-pages-apex-redirect&#x2F;&lt;&#x2F;code&gt;, &lt;code&gt;&#x2F;firebase-hosting-apex-redirect&#x2F;&lt;&#x2F;code&gt;, and &lt;code&gt;&#x2F;heroku-apex-domain-redirect&#x2F;&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;stack&quot;&gt;Stack&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hetzner ARM64&lt;&#x2F;strong&gt; servers, for cheap, low-power compute. ARM64 is ~30% cheaper per vCPU at Hetzner than x86 and runs the redirector fine.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Caddy-style automatic TLS&lt;&#x2F;strong&gt; with &lt;strong&gt;Let’s Encrypt&lt;&#x2F;strong&gt; HTTP-01 challenges.&lt;&#x2F;li&gt;
&lt;li&gt;Dual-stack &lt;strong&gt;IPv4 + IPv6&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;HTTP&#x2F;1.1, HTTP&#x2F;2, HTTP&#x2F;3&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;Public &lt;strong&gt;static marketing site&lt;&#x2F;strong&gt; on Cloudflare Pages, with per-platform guides in separate URL paths so they each rank independently on Google for “netlify apex redirect”, “vercel apex domain”, etc.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;faq&quot;&gt;FAQ&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;can-i-use-apextowww-with-cloudflare-dns&quot;&gt;Can I use apextowww with Cloudflare DNS?&lt;&#x2F;h3&gt;
&lt;p&gt;Yes, but you’ll want Cloudflare’s proxy (orange cloud) &lt;strong&gt;off&lt;&#x2F;strong&gt; for the apex &lt;code&gt;A&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;AAAA&lt;&#x2F;code&gt; records. If the proxy is on, Cloudflare intercepts the request and Let’s Encrypt’s HTTP-01 challenge won’t reach apextowww. Once the certificate is issued and renewed, you don’t need to toggle anything manually; just keep it off.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;does-apextowww-support-wildcards-or-redirecting-subdomains-other-than-www&quot;&gt;Does apextowww support wildcards or redirecting subdomains other than www?&lt;&#x2F;h3&gt;
&lt;p&gt;No. The service only redirects the apex to &lt;code&gt;www.&lt;&#x2F;code&gt;. Everything else stays on your real host.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-happens-if-the-apextowww-ips-change&quot;&gt;What happens if the apextowww IPs change?&lt;&#x2F;h3&gt;
&lt;p&gt;You’ll need to update your DNS &lt;code&gt;A&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;AAAA&lt;&#x2F;code&gt; records. The operator publishes current IPs on the homepage and gives advance notice for changes. For a personal domain this is fine; for anything mission-critical, run your own redirector.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;is-there-a-rate-limit&quot;&gt;Is there a rate limit?&lt;&#x2F;h3&gt;
&lt;p&gt;There’s no published hard limit, but this is a free community service. If you’re serving millions of apex redirects a day, self-host.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;why-not-just-use-cloudflare-s-free-plan&quot;&gt;Why not just use Cloudflare’s free plan?&lt;&#x2F;h3&gt;
&lt;p&gt;Cloudflare’s CNAME flattening at the apex is a perfectly reasonable alternative if your whole DNS is on Cloudflare. apextowww is useful when your DNS isn’t on Cloudflare, or when you want a host-agnostic redirector that works identically across domains.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;is-it-really-free&quot;&gt;Is it really free?&lt;&#x2F;h3&gt;
&lt;p&gt;Yes. The marginal cost per redirect on ARM64 is negligible. No ads, no tracking beyond basic logs, no upsell.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-it-exists&quot;&gt;Why it exists&lt;&#x2F;h2&gt;
&lt;p&gt;Hosting platforms optimise for their own onboarding, not for the DNS truisms that trip up every new user. Sending people to a stranger’s VPS felt worse than running one myself. Now my own apex domains (including &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.x2q.net&quot;&gt;x2q.net&lt;&#x2F;a&gt;) point at apextowww, which means I could tear down the last little redirector VPS I still had running for historical reasons.&lt;&#x2F;p&gt;
&lt;p&gt;It’s the kind of project that isn’t interesting until you need it, and then it’s the only thing you need.&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
