<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Notes</title>
    <subtitle>Organized thoughts &amp; experiments</subtitle>
    <link rel="self" type="application/atom+xml" href="https://blog.boers.email/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://blog.boers.email"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-04-07T00:00:00+00:00</updated>
    <id>https://blog.boers.email/atom.xml</id>
    <entry xml:lang="en">
        <title>Building a Custom NixOS Router for Freedom Internet</title>
        <published>2026-04-07T00:00:00+00:00</published>
        <updated>2026-04-07T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.boers.email/posts/freedom-internet-nixos/"/>
        <id>https://blog.boers.email/posts/freedom-internet-nixos/</id>
        
        <content type="html" xml:base="https://blog.boers.email/posts/freedom-internet-nixos/">&lt;p&gt;Due to the persistent lack of IPv6 support at Odido, I finally made the switch to &lt;strong&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;freedom.nl&quot;&gt;Freedom Internet&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt;. My primary goal was to learn how IPv6 works, and frankly, I was done with Odido&#x27;s lax attitude toward their customers.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;from-pfsense-to-nixos&quot;&gt;From pfSense to NixOS&lt;&#x2F;h2&gt;
&lt;p&gt;My initial setup was running pfSense 2.7. The transition to Freedom was actually seamless at first; after configuring DHCPv6, I had an IPv6 connection immediately. However, things went south when I decided to update to version 2.8 to stay current. Suddenly &lt;em&gt;-bam-&lt;&#x2F;em&gt; my router hung indefinitely while loading &lt;code&gt;if_pppoe&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I had been wanting to try &lt;strong&gt;OPNsense&lt;&#x2F;strong&gt; for a while, so I took the opportunity to install it, but I simply couldn&#x27;t get it online. I reinstalled pfSense 2.7, but strangely encountered the same issues. That’s when I had the &quot;brilliant&quot; idea to build my own router from scratch using &lt;strong&gt;NixOS&lt;&#x2F;strong&gt;. After 15 hours of debugging, scouring blog posts, and plenty of &quot;vibecoding,&quot; I finally achieved a working dual-stack connection!&lt;&#x2F;p&gt;
&lt;p&gt;This blog was written after a couple of months using this setup and adjusted with changes beyond my initial setup.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;technical-notes&quot;&gt;Technical Notes&lt;&#x2F;h2&gt;
&lt;p&gt;For those looking to attempt a similar NixOS setup, here are the key takeaways from my configuration:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PPPD for IPv4:&lt;&#x2F;strong&gt; I use &lt;code&gt;pppd&lt;&#x2F;code&gt; to manage the entire IPv4 connection (IP, routing, and DNS).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;systemd-networkd for IPv6:&lt;&#x2F;strong&gt; &lt;code&gt;systemd-networkd&lt;&#x2F;code&gt; handles the full IPv6 stack and all LAN interfaces.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;VLAN Tagging:&lt;&#x2F;strong&gt; The PPPoE connection runs over a VLAN with &lt;strong&gt;ID 6&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;MTU Settings:&lt;&#x2F;strong&gt; On the physical WAN port, I set &lt;code&gt;linkConfig.MTUBytes = &quot;1508&quot;&lt;&#x2F;code&gt; to accommodate the PPPoE overhead while maintaining 1500 byte packets.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;PPPoE Config:&lt;&#x2F;strong&gt; I set both &lt;code&gt;mtu&lt;&#x2F;code&gt; and &lt;code&gt;mru&lt;&#x2F;code&gt; to 1500 in the &lt;code&gt;pppd&lt;&#x2F;code&gt; configuration.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;IPv4 Gateway:&lt;&#x2F;strong&gt; I use &lt;code&gt;defaultroute&lt;&#x2F;code&gt; to let &lt;code&gt;pppd&lt;&#x2F;code&gt; set the IPv4 gateway automatically.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;DNS:&lt;&#x2F;strong&gt; I run &lt;strong&gt;AdGuard Home&lt;&#x2F;strong&gt; as my local DNS resolver with Freedom&#x27;s DNS-over-TLS (&lt;code&gt;dns.freedom.nl&lt;&#x2F;code&gt;) and Quad9 as upstreams. This provides encrypted DNS queries, local caching, and network-wide ad&#x2F;tracker blocking without client-side configuration.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;IPv6 Solicit:&lt;&#x2F;strong&gt; Setting &lt;code&gt;WithoutRA = &quot;solicit&quot;&lt;&#x2F;code&gt; is important because Freedom doesn&#x27;t send Router Advertisements; you have to start with DHCPv6 immediately.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Manual IPv6 Route:&lt;&#x2F;strong&gt; I manually set the default IPv6 route using &lt;code&gt;routes = [{ Gateway = &quot;::&quot;; ... }];&lt;&#x2F;code&gt; because Freedom does not provide this via DHCPv6.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Race Conditions:&lt;&#x2F;strong&gt; I applied &lt;code&gt;linkConfig.RequiredForOnline = &quot;yes&quot;;&lt;&#x2F;code&gt; to the &lt;code&gt;pppd&lt;&#x2F;code&gt; interface to resolve a race condition between &lt;code&gt;pppd&lt;&#x2F;code&gt; and &lt;code&gt;systemd-networkd&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;CAKE QoS:&lt;&#x2F;strong&gt; I use the CAKE queue discipline for traffic shaping at 950 Mbps. This includes NAT awareness, ACK filtering, and handling for the PPPoE overhead to keep bufferbloat low while maintaining high throughput.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;ipv6-prefix-delegation&quot;&gt;IPv6 Prefix Delegation&lt;&#x2F;h2&gt;
&lt;p&gt;Freedom provides a &lt;code&gt;&#x2F;48&lt;&#x2F;code&gt; IPv6 prefix via DHCPv6-PD. Configuring this with systemd-networkd is straightforward. On each LAN interface, I enable prefix delegation and router advertisements:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);&quot;&gt;&lt;code data-lang=&quot;nix&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: light-dark(#E36209, #FFAB70);&quot;&gt;networkConfig&lt;&#x2F;span&gt;&lt;span style=&quot;color: light-dark(#B31D28, #FDAEB7);font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: light-dark(#6F42C1, #B392F0);&quot;&gt;  IPv6SendRA&lt;&#x2F;span&gt;&lt;span style=&quot;color: light-dark(#D73A49, #F97583);&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: light-dark(#032F62, #9ECBFF);&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: light-dark(#032F62, #9ECBFF);&quot;&gt;yes&lt;&#x2F;span&gt;&lt;span style=&quot;color: light-dark(#032F62, #9ECBFF);&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: light-dark(#6F42C1, #B392F0);&quot;&gt;  DHCPPrefixDelegation&lt;&#x2F;span&gt;&lt;span style=&quot;color: light-dark(#D73A49, #F97583);&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: light-dark(#032F62, #9ECBFF);&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: light-dark(#032F62, #9ECBFF);&quot;&gt;yes&lt;&#x2F;span&gt;&lt;span style=&quot;color: light-dark(#032F62, #9ECBFF);&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: light-dark(#B31D28, #FDAEB7);font-style: italic;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;systemd-networkd then:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Requests the prefix from the upstream WAN interface&lt;&#x2F;li&gt;
&lt;li&gt;Automatically assigns a &lt;code&gt;&#x2F;64&lt;&#x2F;code&gt; subnet to the LAN interface&lt;&#x2F;li&gt;
&lt;li&gt;Sends Router Advertisements so devices can auto-configure addresses&lt;&#x2F;li&gt;
&lt;li&gt;Handles renewals transparently&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;This results in devices getting globally routable IPv6 addresses without any manual subnet management or NAT configuration.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;nftables-learning-by-doing&quot;&gt;nftables: Learning by Doing&lt;&#x2F;h2&gt;
&lt;p&gt;I’ll be the first to admit I don’t know enough about nftables yet, but writing things neatly into my NixOS configuration give me extra motivation to have a deeper understanding of such things. For example setting time-based access control for my robot vacuum:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;iifname &amp;quot;wifi&amp;quot; ip saddr ${dreame} oifname &amp;quot;peepee&amp;quot; meta hour &amp;quot;10:00&amp;quot;-&amp;quot;19:00&amp;quot; accept comment &amp;quot;Dreame Internet Access Window&amp;quot;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;iifname &amp;quot;wifi&amp;quot; ip saddr ${dreame} oifname &amp;quot;peepee&amp;quot; drop comment &amp;quot;Block Dreame Internet outside schedule&amp;quot;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now it can only phone home between 10 AM and 7 PM. I run &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.ntop.org&#x2F;products&#x2F;traffic-analysis&#x2F;ntopng&#x2F;&quot;&gt;ntopng&lt;&#x2F;a&gt; on this same router and noticed it&#x27;s phoning home to Chinese Alibaba cloud servers all day long. This rule gives it a strict time window for internet access while keeping it blocked the rest of the day.&lt;&#x2F;p&gt;
&lt;p&gt;Other fun rules include:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git.boers.email&#x2F;nodes&#x2F;seed.boers.email&#x2F;rad:z2Jkf9zxGPxEhCfGLpgRAcHRj8x2n&#x2F;tree&#x2F;5411bfe04757ea9688555fb3fdd5ae5657481f0f&#x2F;hosts&#x2F;dosukoi&#x2F;modules&#x2F;firewall.nix#L75&quot;&gt;Inter-VLAN routing&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt; with granular controls—my management VLAN can reach everything, but IoT devices are restricted&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git.boers.email&#x2F;nodes&#x2F;seed.boers.email&#x2F;rad:z2Jkf9zxGPxEhCfGLpgRAcHRj8x2n&#x2F;tree&#x2F;5411bfe04757ea9688555fb3fdd5ae5657481f0f&#x2F;hosts&#x2F;dosukoi&#x2F;modules&#x2F;firewall.nix#L31&quot;&gt;Dynamic blocklist&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt; integration that drops traffic from known malicious IPs and web scanning services like Shodan.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;hardware-and-performance&quot;&gt;Hardware and Performance&lt;&#x2F;h2&gt;
&lt;p&gt;The router runs on a &lt;strong&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;eu.protectli.com&#x2F;products&#x2F;v1410&#x2F;&quot;&gt;Protectli V1410&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt;, a fanless mini PC with four 2.5GbE ports and an Intel N100 CPU. It&#x27;s silent, low-power, and more than capable of handling gigabit fiber with all the services I run on it.&lt;&#x2F;p&gt;
&lt;p&gt;Performance-wise, the setup is pretty solid: I’m currently hitting &lt;strong&gt;910 Mbps down and ~830 Mbps up&lt;&#x2F;strong&gt;. Good, but I&#x27;ve had pretty consistent 1Gbps up&#x2F;down on my Odido line using their ONT and router.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;configuration-files&quot;&gt;Configuration Files&lt;&#x2F;h2&gt;
&lt;p&gt;If you want to see the code behind this setup, you can find my NixOS modules here:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git.boers.email&#x2F;nodes&#x2F;seed.boers.email&#x2F;rad:z2Jkf9zxGPxEhCfGLpgRAcHRj8x2n&#x2F;tree&#x2F;5411bfe04757ea9688555fb3fdd5ae5657481f0f&#x2F;hosts&#x2F;dosukoi&#x2F;modules&#x2F;interfaces.nix&quot;&gt;Network Interfaces Configuration&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git.boers.email&#x2F;nodes&#x2F;seed.boers.email&#x2F;rad:z2Jkf9zxGPxEhCfGLpgRAcHRj8x2n&#x2F;tree&#x2F;5411bfe04757ea9688555fb3fdd5ae5657481f0f&#x2F;hosts&#x2F;dosukoi&#x2F;modules&#x2F;firewall.nix&quot;&gt;Firewall Configuration&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;em&gt;Note: These files will likely evolve. If you&#x27;re reading this in the future, be sure to check the master branch for the latest updates.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;resources&quot;&gt;Resources&lt;&#x2F;h3&gt;
&lt;p&gt;I&#x27;ve leaned on multiple online blogs and resources, in no particular order:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.sput.nl&#x2F;internet&#x2F;freedom&#x2F;config.html&quot;&gt;sput.nl&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;s-n.me&#x2F;building-a-nixos-router-for-a-uk-fttp-isp-the-basics&quot;&gt;s-n.me&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.jjpdev.com&#x2F;posts&#x2F;home-router-nixos&#x2F;&quot;&gt;jjpdev&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Is this thing on?</title>
        <published>2026-04-06T00:00:00+00:00</published>
        <updated>2026-04-06T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.boers.email/posts/let-there-be-posts/"/>
        <id>https://blog.boers.email/posts/let-there-be-posts/</id>
        
        <content type="html" xml:base="https://blog.boers.email/posts/let-there-be-posts/">&lt;h2 id=&quot;are-we-live&quot;&gt;Are we live?&lt;&#x2F;h2&gt;
&lt;p&gt;I think so? Happy blogging&lt;&#x2F;p&gt;




&lt;img alt=&quot;Test image&quot; title=&quot;Test image&quot; src=&quot;https:&#x2F;&#x2F;blog.boers.email&#x2F;processed_images&#x2F;1.c176694bc50d8f20.png&quot; srcset=&quot;https:&#x2F;&#x2F;blog.boers.email&#x2F;processed_images&#x2F;1.d84c65bbbb101c54.png 512w&quot; class=&quot;&quot; &#x2F;&gt;

&lt;pre class=&quot;giallo&quot; style=&quot;color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: light-dark(#005CC5, #79B8FF);&quot;&gt;print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: light-dark(#6F42C1, #B392F0);&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: light-dark(#6F42C1, #B392F0);&quot;&gt;pythonenv&lt;&#x2F;span&gt;&lt;span style=&quot;color: light-dark(#6F42C1, #B392F0);&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;</content>
        
    </entry>
</feed>
