{
  "version": "https://jsonfeed.org/version/1",
  "title": "Ian's Digital Garden",
  "home_page_url": "https://ianwwagner.com/",
  "feed_url": "https://ianwwagner.com//tag-freebsd.json",
  "description": "",
  "items": [
    {
      "id": "https://ianwwagner.com//setting-up-a-wireguard-tunnel-on-freebsd-15.html",
      "url": "https://ianwwagner.com//setting-up-a-wireguard-tunnel-on-freebsd-15.html",
      "title": "Setting up a WireGuard Tunnel on FreeBSD 15",
      "content_html": "<p>It's not like the world needs yet another WireGuard tutorial,\nbut I thought I'd write one since one of the top SEO-ranked ones I stumbled upon was pretty low quality,\nwith several obvious errors and omissions.</p>\n<p>In this post, I'll focus on how you can set up a VPN tunnel\nin the sense that such things were used before shady companies hijacked the term.\nIt's just a way to tunnel traffic between networks.\nFor example, to connect non-internet-facing servers behind a firewall\nto a public host that firewalls and selectively routes traffic over the tunnel.</p>\n<p>I'll assume a pair of FreeBSD servers for the rest of the post,\none that's presumably more accessible (the &quot;server&quot;),\nand a client which is not necessarily routable over the internet.</p>\n<h1><a href=\"#server-setup\" aria-hidden=\"true\" class=\"anchor\" id=\"server-setup\"></a>&quot;Server&quot; setup</h1>\n<p>We'll start with the server setup.\nThis is where your client(s) will connect.\nAt a high level, we'll generate a keypair for the server,\na keypair for the client,\nand generate configuration files for both.\nAnd finally we'll do some basic firewall configuration.</p>\n<h2><a href=\"#wireguard-config\" aria-hidden=\"true\" class=\"anchor\" id=\"wireguard-config\"></a>WireGuard config</h2>\n<p>The following can be run,\neither in a script or line-by-line in a POSIX shell as root.</p>\n<pre><code class=\"language-sh\"># Set this to your server's public IP\nSERVER_PUBLIC_IP=&quot;192.0.2.42&quot;\n\n# We'll be setting up some config files here that we only want to be readable by root.\n# The umask saves us the effort of having to chmod these later.\numask 077\n\n# Wireguard kernel-level support is available in FreeBSD 14+,\n# but this port has a nice service wrapper\npkg install wireguard-tools\n\n# Set up WireGuard config directory\nchmod 770 /usr/local/etc/wireguard\ncd /usr/local/etc/wireguard\n\n# Create a keypair for the server\nSERVER_PRIV_KEY=$(wg genkey)\nSERVER_PUB_KEY=`echo $SERVER_PRIV_KEY | wg pubkey`\n\n# Generate the first section of our WireGuard server config.\n# We'll use 172.16.0.1/24 (no real reason for the choice;\n# it's just somewhat convenient as it doesn't collide with the more common\n# Class A and Class C private networks).\ncat &gt; wg0.conf &lt;&lt;EOF\n[Interface]\nAddress = 172.16.0.1/24\nSaveConfig = true\nListenPort = 51820\nPrivateKey = ${SERVER_PRIV_KEY}\nEOF\n\n# Similarly, we need a client keypair\nCLIENT_PRIV_KEY=$(wg genkey)\nCLIENT_PUB_KEY=`echo $CLIENT_PRIV_KEY | wg pubkey`\n\n# Add peer to the server config.\n# This is what lets your client connect later.\n# The server only stores the client's public key\n# and the private IP that it will connect as.\nCLIENT_IP=&quot;172.16.0.2&quot;\ncat &gt;&gt; wg0.conf &lt;&lt;EOF\n# bsdcube\n[Peer]\nPublicKey = ${CLIENT_PUB_KEY}\nAllowedIps = ${CLIENT_IP}/32\nEOF\n\numask 022 # Revert to normal umask\n\n# Enable the wireguard service\nsysrc wireguard_interfaces=&quot;wg0&quot;\nsysrc wireguard_enable=&quot;YES&quot;\nservice wireguard start\n</code></pre>\n<p><strong>Don't ditch this shell session yet!</strong>\nWe'll come back to the client config later and will need the vars defined above.\nBut first, a brief interlude for packet filtering.</p>\n<h2><a href=\"#pf-setup\" aria-hidden=\"true\" class=\"anchor\" id=\"pf-setup\"></a><code>pf</code> setup</h2>\n<p>We'll use <code>pf</code>, the robust packet filtering (colloquially &quot;firewall&quot;) system\nported from OpenBSD.</p>\n<p>I'm using <code>vtnet0</code> for the external interface,\nsince that's the interface name with my VPS vendor.\nYou may need to change this based on what your main network interface is\n(check <code>ifconfig</code>).</p>\n<p><strong>DISCLAIMER</strong>: This is <em>not</em> necessarily everything you need to launch a production system.\nI've distilled just the parts that are relevant to a minimal WireGuard setup.\nThat said, here's a minimal <code>/etc/pf.conf</code>.</p>\n<pre><code class=\"language-pf\">ext_if = &quot;vtnet0&quot;\nwg_if = &quot;wg0&quot;\n\n# Pass all traffic on the loopback interface\nset skip on lo\n\n# Basic packet cleanup\nscrub in on $ext_if all fragment reassemble\n\n# Allows WireGuard clients to reach the internet.\n# I do not nede this in my config, but noting it here\n# in case your use case is *that* sort of VPN.\n# nat on $ext_if from $wg_if:network to any -&gt; ($ext_if)\n\n# Allow all outbound connections\npass out keep state\n\n# SSH (there's a good chance you need this)\npass in on $ext_if proto tcp from any to ($ext_if) port 22\n\n# Allow inbound WireGuard traffic\npass in on $ext_if proto udp from any to ($ext_if) port 51820\n\n# TODO: Forwarding for the services that YOU need\n# Here's one example demonstrating how you would allow traffic\n# to route directly to one of the WireGuard network IPs (e.g. 172.16.42.1/24 in this example)\n# over port 8080.\n# pass in on $wg_if proto tcp from $wg_if:network to ($wg_if) port 8080\n\n# Allow ICMP\npass in inet proto icmp all\npass in inet6 proto icmp6 all\n</code></pre>\n<p>Next, we enable the service and start it.\nIf you're already running <code>pf</code>, then at least part of this isn't necessary.</p>\n<pre><code class=\"language-sh\"># Allow forwarding of traffic from from WireGuard clients\nsysctl net.inet.ip.forwarding=1\n\n# Enable pf\nsysrc pf_enable=&quot;YES&quot;\nservice pf start\n</code></pre>\n<h1><a href=\"#client-configuration\" aria-hidden=\"true\" class=\"anchor\" id=\"client-configuration\"></a>Client configuration</h1>\n<p>And now we come back to the client configuration.\nThe &quot;client&quot; in this case does not necessarily have to be routable over the internet;\nit just needs to be able to connect to the server.\nYou've still got the same shell session with those variables, right?</p>\n<pre><code class=\"language-sh\">cat &lt;&lt;EOF\n[Interface]\nPrivateKey = ${CLIENT_PRIV_KEY}\nAddress = ${CLIENT_IP}/24\n\n[Peer]\nPublicKey = ${SERVER_PUB_KEY}\nAllowedIPs = 172.16.0.0/24  # Only route private subnet traffic over the tunnel\nEndpoint = ${SERVER_PUBLIC_IP}:51820\nPersistentKeepalive = 30\nEOF\n</code></pre>\n<p>That's it; that's the client config.\nRun through the same initial setup steps for adding the <code>wireguard-tools</code> package\nand creating the directory with the right permissions.\nThen put this config in <code>/usr/local/etc/wireguard/wg0.conf</code>.</p>\n<p>The client will also need a similar <code>pf</code> configuration,\nbut rather than blanket allowing traffic in over <code>$wg_if</code>,\nyou probably want something a bit more granular.\nFor example, allowing traffic in over a specific port (e.g. <code>8080</code>).\nI'll leave that as an exercise to the reader based on the specific scenario.</p>\n",
      "summary": "",
      "date_published": "2026-04-07T00:00:00-00:00",
      "image": "",
      "authors": [
        {
          "name": "Ian Wagner",
          "url": "https://fosstodon.org/@ianthetechie",
          "avatar": "media/avi.jpeg"
        }
      ],
      "tags": [
        "networking",
        "FreeBSD"
      ],
      "language": "en"
    }
  ]
}