<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mišo Oroz</title>
    <description>The latest articles on DEV Community by Mišo Oroz (@miso_oroz).</description>
    <link>https://dev.to/miso_oroz</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4106374%2Ffb027a7a-606f-45e1-affe-07aebb734d0c.jpg</url>
      <title>DEV Community: Mišo Oroz</title>
      <link>https://dev.to/miso_oroz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/miso_oroz"/>
    <language>en</language>
    <item>
      <title>WireGuard VPN for Production: The Exact Setup I Deploy for Small Businesses</title>
      <dc:creator>Mišo Oroz</dc:creator>
      <pubDate>Tue, 15 Sep 2026 09:11:45 +0000</pubDate>
      <link>https://dev.to/zerolock/wireguard-vpn-for-production-the-exact-setup-i-deploy-for-small-businesses-3m41</link>
      <guid>https://dev.to/zerolock/wireguard-vpn-for-production-the-exact-setup-i-deploy-for-small-businesses-3m41</guid>
      <description>&lt;p&gt;WireGuard is the VPN I deploy for every small business I migrate to Linux. Not because it is trendy, but because it is the most honest piece of networking software I have worked with in fifteen years.&lt;/p&gt;

&lt;p&gt;In 2026, WireGuard is the default VPN protocol for a reason: it is in the mainline Linux kernel since version 5.6, independently audited, and faster than OpenVPN on the same hardware. For small businesses this means no kernel modules to maintain, no CVE backlog, and no performance complaints.&lt;/p&gt;

&lt;p&gt;The entire trust model fits in a single config file. When something breaks, you can audit it in minutes. That matters at 8am on a Monday when a client cannot connect and you need to diagnose fast.&lt;/p&gt;

&lt;p&gt;This is not a theoretical walkthrough. This is exactly what I deploy in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you start
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Server with static public IP&lt;/li&gt;
&lt;li&gt;UDP 51820 open on server firewall&lt;/li&gt;
&lt;li&gt;SSH access to the server&lt;/li&gt;
&lt;li&gt;Client machine with working internet connection&lt;/li&gt;
&lt;li&gt;A clear decision on whether all traffic routes through the tunnel or only the internal network (more on this below)&lt;/li&gt;
&lt;li&gt;A backup access plan if the tunnel breaks (physical access, out-of-band SSH, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why WireGuard specifically
&lt;/h2&gt;

&lt;p&gt;The WireGuard codebase is roughly 4,000 lines. OpenVPN's core daemon is closer to 70,000. This is not a marketing detail. Smaller codebase means smaller attack surface, faster patching, and a security posture you can actually reason about.&lt;/p&gt;

&lt;p&gt;WireGuard has been independently audited. The Cure53 audit in 2021 and the Trail of Bits review found no critical vulnerabilities. OpenVPN has also been audited, but its larger codebase makes comprehensive auditing more time-consuming and leaves more surface area for potential issues.&lt;/p&gt;

&lt;p&gt;I evaluated three options before standardizing on WireGuard:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenVPN:&lt;/strong&gt; mature, flexible, but complex. Requires certificate management, has a larger attack surface, and is noticeably slower on low-end hardware.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IPsec/IKEv2:&lt;/strong&gt; fast and secure, but configuration is opaque. Debugging failures often requires deep protocol knowledge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WireGuard:&lt;/strong&gt; minimal, audited, fast. The trade-off is manual key management, which is acceptable for teams under 20 people.&lt;/p&gt;

&lt;p&gt;For small businesses that cannot afford enterprise security teams, choosing audited software with a minimal codebase is one of the few concrete security decisions they can make.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need
&lt;/h2&gt;

&lt;p&gt;A Linux server you control. In my case a VPS hosted in Italy, running Debian 12. A static IP on the server side. A client machine running Linux, in most cases Kubuntu 26.04 LTS.&lt;/p&gt;

&lt;p&gt;I use a VPS for the relay in most deployments because it gives clients a stable public IP without managing physical hardware at the office. For businesses with an on-premise server already in place, running WireGuard directly on that machine works equally well. The config is identical.&lt;/p&gt;

&lt;p&gt;Debian 12 is the default for server deployments: predictable release cycle, five years of security updates, no surprises. I avoid rolling releases on anything client-facing.&lt;/p&gt;

&lt;p&gt;WireGuard uses public key cryptography. Each peer generates a key pair. You exchange public keys. No certificates, no certificate authorities, no complexity that lives outside the config file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Server setup
&lt;/h2&gt;

&lt;p&gt;Install WireGuard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;wireguard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate the server key pair. Set permissions before the key is used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wg genkey &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/wireguard/privatekey
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 /etc/wireguard/privatekey
wg pubkey &amp;lt; /etc/wireguard/privatekey &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/wireguard/publickey
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the server configuration at &lt;code&gt;/etc/wireguard/wg0.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Interface]&lt;/span&gt;
&lt;span class="py"&gt;PrivateKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;server_private_key&amp;gt;&lt;/span&gt;
&lt;span class="py"&gt;Address&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;10.0.0.1/24&lt;/span&gt;
&lt;span class="py"&gt;ListenPort&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;51820&lt;/span&gt;
&lt;span class="py"&gt;PostUp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o &amp;lt;interface&amp;gt; -j MASQUERADE&lt;/span&gt;
&lt;span class="py"&gt;PostDown&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o &amp;lt;interface&amp;gt; -j MASQUERADE&lt;/span&gt;

&lt;span class="nn"&gt;[Peer]&lt;/span&gt;
&lt;span class="py"&gt;PublicKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;client_public_key&amp;gt;&lt;/span&gt;
&lt;span class="py"&gt;AllowedIPs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;10.0.0.2/32&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Replace &lt;code&gt;&amp;lt;interface&amp;gt;&lt;/code&gt; with your actual network interface name.&lt;/strong&gt; On Debian 12 modern installs this is typically &lt;code&gt;ens3&lt;/code&gt;, &lt;code&gt;enp1s0&lt;/code&gt;, or similar. Check with &lt;code&gt;ip link&lt;/code&gt; before editing the config. Almost never &lt;code&gt;eth0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why 10.0.0.0/24?&lt;/strong&gt; This is RFC 1918 private space, unlikely to conflict with most office networks. I use 10.0.0.1 for the server and increment client IPs from 10.0.0.2. If your office already uses 10.0.0.0/24, choose a different range such as 10.100.0.0/24 to avoid routing conflicts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On the &lt;code&gt;/32&lt;/code&gt; for each peer:&lt;/strong&gt; this tells WireGuard that this specific public key is only allowed to use the IP 10.0.0.2. Each peer must have its own &lt;code&gt;/32&lt;/code&gt; entry. Without it, a compromised client could potentially spoof other addresses on the tunnel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note on cloud providers:&lt;/strong&gt; if you use a VPS with a cloud security group (Hetzner, DigitalOcean, AWS), open UDP 51820 in the security group rather than via iptables. The PostUp rules above handle forwarding and NAT, not port access itself.&lt;/p&gt;

&lt;p&gt;If your server has IPv6 enabled, which is common on modern VPS, add ip6tables rules as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;PostUp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o &amp;lt;interface&amp;gt; -j MASQUERADE; ip6tables -A FORWARD -i wg0 -j ACCEPT; ip6tables -A FORWARD -o wg0 -j ACCEPT; ip6tables -t nat -A POSTROUTING -o &amp;lt;interface&amp;gt; -j MASQUERADE&lt;/span&gt;
&lt;span class="py"&gt;PostDown&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o &amp;lt;interface&amp;gt; -j MASQUERADE; ip6tables -D FORWARD -i wg0 -j ACCEPT; ip6tables -D FORWARD -o wg0 -j ACCEPT; ip6tables -t nat -D POSTROUTING -o &amp;lt;interface&amp;gt; -j MASQUERADE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable IP forwarding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'net.ipv4.ip_forward = 1'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/sysctl.conf
sysctl &lt;span class="nt"&gt;-p&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable and start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; wg-quick@wg0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Client setup
&lt;/h2&gt;

&lt;p&gt;Install WireGuard and openresolv:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;wireguard openresolv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate client key pair:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wg genkey &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/wireguard/privatekey
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 /etc/wireguard/privatekey
wg pubkey &amp;lt; /etc/wireguard/privatekey &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/wireguard/publickey
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;/etc/wireguard/wg0.conf&lt;/code&gt; on the client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Interface]&lt;/span&gt;
&lt;span class="py"&gt;PrivateKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;client_private_key&amp;gt;&lt;/span&gt;
&lt;span class="py"&gt;Address&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;10.0.0.2/32&lt;/span&gt;
&lt;span class="py"&gt;DNS&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1.1.1.1&lt;/span&gt;

&lt;span class="nn"&gt;[Peer]&lt;/span&gt;
&lt;span class="py"&gt;PublicKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;server_public_key&amp;gt;&lt;/span&gt;
&lt;span class="py"&gt;Endpoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;server_public_ip&amp;gt;:51820&lt;/span&gt;
&lt;span class="py"&gt;AllowedIPs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;0.0.0.0/0, ::/0&lt;/span&gt;
&lt;span class="py"&gt;PersistentKeepalive&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;25&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why &lt;code&gt;/32&lt;/code&gt; on the client?&lt;/strong&gt; The client is a single host, not a subnet. Using &lt;code&gt;/32&lt;/code&gt; is semantically correct and avoids confusion when adding more clients later. The &lt;code&gt;/24&lt;/code&gt; on the server interface is correct because it represents the entire tunnel network.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;0.0.0.0/0, ::/0&lt;/code&gt; routes all traffic through the VPN, including IPv6. If you only need access to the internal network without routing everything, use the specific subnet instead, for example &lt;code&gt;10.0.0.0/24&lt;/code&gt;. Understand what you are routing before you route it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PersistentKeepalive = 25&lt;/code&gt; sends a keepalive every 25 seconds. Required when the client is behind NAT, which is almost always the case for small business remote workers. Without it the NAT mapping expires and the tunnel silently drops.&lt;/p&gt;

&lt;p&gt;Enable and start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; wg-quick@wg0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the tunnel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wg show
ping 10.0.0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;wg show&lt;/code&gt; confirms the handshake happened. &lt;code&gt;ping 10.0.0.1&lt;/code&gt; confirms the tunnel carries traffic. Both should work before you call it done.&lt;/p&gt;

&lt;h2&gt;
  
  
  DNS on Kubuntu 26.04 with systemd-resolved
&lt;/h2&gt;

&lt;p&gt;Kubuntu 26.04 uses systemd-resolved by default. The &lt;code&gt;DNS =&lt;/code&gt; directive in wg0.conf works correctly with systemd-resolved, but you must ensure &lt;code&gt;/etc/resolv.conf&lt;/code&gt; points to the stub resolver:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /etc/resolv.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;/&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;resolv&lt;/span&gt;.&lt;span class="n"&gt;conf&lt;/span&gt; -&amp;gt; ../&lt;span class="n"&gt;run&lt;/span&gt;/&lt;span class="n"&gt;systemd&lt;/span&gt;/&lt;span class="n"&gt;resolve&lt;/span&gt;/&lt;span class="n"&gt;stub&lt;/span&gt;-&lt;span class="n"&gt;resolv&lt;/span&gt;.&lt;span class="n"&gt;conf&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it points elsewhere, fix it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-sf&lt;/span&gt; /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bring up the tunnel and verify DNS is routing correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;resolvectl status wg0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see your configured DNS listed under the wg0 interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify no DNS leaks
&lt;/h2&gt;

&lt;p&gt;A DNS leak means your DNS queries are going outside the tunnel, exposing which domains you visit even if the traffic itself is encrypted. After bringing up the tunnel, verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig +short myip.opendns.com @resolver1.opendns.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should return your VPN server's public IP, not your client's real IP. If it returns your real IP, DNS is leaking.&lt;/p&gt;

&lt;p&gt;Also check for IPv6 leaks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-6&lt;/span&gt; ifconfig.co
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this returns an IP, IPv6 traffic is not going through the tunnel. Either disable IPv6 on the client or ensure &lt;code&gt;::/0&lt;/code&gt; is in &lt;code&gt;AllowedIPs&lt;/code&gt; and add an IPv6 DNS server: &lt;code&gt;DNS = 1.1.1.1, 2606:4700:4700::1111&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key management
&lt;/h2&gt;

&lt;p&gt;WireGuard has no built-in key rotation. When an employee leaves or a device is compromised, you must manually remove the &lt;code&gt;[Peer]&lt;/code&gt; block from the server config and redistribute new keys.&lt;/p&gt;

&lt;p&gt;My workflow for small businesses: I keep a simple spreadsheet with peer name (for example "alice-laptop"), public key copied from &lt;code&gt;/etc/wireguard/publickey&lt;/code&gt;, device owner, creation date, and last rotation date. Rotate keys every 12 to 18 months as standard practice. If a device is lost, revoke immediately: remove the &lt;code&gt;[Peer]&lt;/code&gt; block, restart WireGuard, and generate new keys for the affected client.&lt;/p&gt;

&lt;p&gt;This manual process is a trade-off: you gain simplicity and auditability, but lose automated lifecycle management. For teams under 20 people it is acceptable. Beyond that, consider a WireGuard management layer like Netmaker or Headscale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring and troubleshooting
&lt;/h2&gt;

&lt;p&gt;Check handshake age — should be under two minutes on an active connection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wg show | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"latest handshake"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Watch live traffic through the tunnel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;watch &lt;span class="nt"&gt;-n1&lt;/span&gt; &lt;span class="s1"&gt;'wg show | grep transfer'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For proactive monitoring I use a simple cron job that pings the tunnel IP every 5 minutes and logs failures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;*&lt;/span&gt;/5 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; ping &lt;span class="nt"&gt;-c1&lt;/span&gt; 10.0.0.1 &lt;span class="o"&gt;||&lt;/span&gt; logger &lt;span class="s2"&gt;"WireGuard tunnel down"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the tunnel drops:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check logs: &lt;code&gt;journalctl -u wg-quick@wg0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Verify the port is reachable: &lt;code&gt;nc -vzu &amp;lt;server_ip&amp;gt; 51820&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Confirm &lt;code&gt;PersistentKeepalive = 25&lt;/code&gt; is set on the client&lt;/li&gt;
&lt;li&gt;Restart the tunnel: &lt;code&gt;systemctl restart wg-quick@wg0&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Appendix: Kubuntu 26.04 and Wayland
&lt;/h2&gt;

&lt;p&gt;Kubuntu 26.04 uses Wayland by default. This does not affect WireGuard itself, but it breaks X11-based remote desktop tools. For full remote access setups I pair WireGuard with a self-hosted RustDesk relay, which handles Wayland correctly. On Kubuntu 24.04, which still uses X11, standard remote desktop tools work without this additional step. RustDesk with a self-hosted relay is outside the scope of this guide but worth knowing before you commit to a 26.04 deployment for remote workers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final notes
&lt;/h2&gt;

&lt;p&gt;I have been running this setup across multiple client deployments since early 2026. No security incidents, no unexplained drops. WireGuard's simplicity is its strength: when something breaks, you can read the entire config and understand it in minutes.&lt;/p&gt;

&lt;p&gt;For small businesses migrating to Linux, this is the VPN layer I standardize on. And if a client or an auditor ever asks why I chose it, I can explain every line of the config. That is not something I could say about most alternatives.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://zerolock.it" rel="noopener noreferrer"&gt;zerolock.it&lt;/a&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>opensource</category>
      <category>vpn</category>
      <category>wireguard</category>
    </item>
    <item>
      <title>Nextcloud vs Google Drive: a practical comparison for small business migrations</title>
      <dc:creator>Mišo Oroz</dc:creator>
      <pubDate>Sun, 06 Sep 2026 14:20:24 +0000</pubDate>
      <link>https://dev.to/zerolock/nextcloud-vs-google-drive-a-practical-comparison-for-small-business-migrations-3obc</link>
      <guid>https://dev.to/zerolock/nextcloud-vs-google-drive-a-practical-comparison-for-small-business-migrations-3obc</guid>
      <description>&lt;p&gt;I've been migrating small businesses in northern Italy from Google Workspace and Microsoft 365 to open source stacks for a while now. One of the first questions I always get is: "What about file storage? We use Google Drive for everything."&lt;/p&gt;

&lt;p&gt;Fair question. Let me give you an honest answer, not a sales pitch.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we're actually comparing
&lt;/h2&gt;

&lt;p&gt;When I say Google Drive in a business context, I mean the full Google Workspace package: Drive, Docs, Sheets, Gmail, Meet. It's a coherent platform that works well. I'm not going to pretend otherwise.&lt;/p&gt;

&lt;p&gt;Nextcloud is a self-hosted (or managed-hosted) file sync and collaboration platform. Out of the box it handles file storage, sharing, and sync across devices. With apps you can add calendar, contacts, talk (video calls), and collaborative document editing via Nextcloud Office (built on Collabora).&lt;/p&gt;

&lt;p&gt;So the comparison isn't just "where do files live." It's about how your client's business actually runs day to day.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real differences that matter
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Data location and sovereignty&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With Google Drive, your files live on Google's infrastructure. Full stop. For most small businesses this isn't a problem until it is: GDPR compliance questions, a client contract that requires data to stay in Italy, or a sector with specific regulations (legal, medical, financial).&lt;/p&gt;

&lt;p&gt;With Nextcloud you choose where data lives. I typically set up clients on Italian or Swiss-based infrastructure, choosing providers that keep data within EU jurisdiction. The client owns the data, period.&lt;/p&gt;

&lt;p&gt;This matters more than people realize. I've had clients lose access to their Google Workspace because of billing issues during a holiday weekend. With self-hosted infrastructure, you control the keys.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost structure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Google Workspace Business Starter runs around 6.80 euros per user per month at the time of writing (check current pricing on their site as this changes). For a team of 8 that's around 653 euros per year, just for the base tier. Storage limits apply at 30 GB pooled per user, and many teams end up on a higher plan within a year.&lt;/p&gt;

&lt;p&gt;Nextcloud itself is open source and free. You pay for hosting. A decent VPS with enough storage for a small team costs 15 to 30 euros per month, depending on specs and provider. For 8 users that's 180 to 360 euros per year, with no per-user licensing.&lt;/p&gt;

&lt;p&gt;The math is straightforward. The savings compound over time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Feature parity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is where people get nervous. Let me be direct: Nextcloud is not Google Drive. Some things work differently, some things work better, some things are more manual.&lt;/p&gt;

&lt;p&gt;File sync across devices works well. The desktop clients for Linux, Windows, and macOS are stable. Mobile apps exist for Android and iOS.&lt;/p&gt;

&lt;p&gt;Collaborative editing works if you install Nextcloud Office. It's not as smooth as Google Docs in my experience, but it's functional for most business use cases. If your team lives inside documents all day, evaluate this carefully before committing.&lt;/p&gt;

&lt;p&gt;Search is decent but not Google-level. For a team that stores lots of documents and searches constantly, this is worth testing with real data before the migration.&lt;/p&gt;

&lt;p&gt;Sharing links, permissions, versioning: all there and well implemented.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Administration overhead&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the honest part. Google Workspace has zero admin overhead for the IT person. You click, it works, Google handles updates, security patches, and uptime.&lt;/p&gt;

&lt;p&gt;Nextcloud requires someone to handle updates, monitor the instance, and occasionally deal with issues. If that someone is you (the IT consultant), you need to factor this into your service contract. If it's supposed to be the client on their own, think carefully.&lt;/p&gt;

&lt;p&gt;For the clients I manage this is part of the monthly support I provide. It's not heavy lifting, but it's not zero either.&lt;/p&gt;

&lt;h2&gt;
  
  
  When I recommend Nextcloud
&lt;/h2&gt;

&lt;p&gt;I push for Nextcloud when the client has 2 to 20 users with straightforward storage and sharing needs and no heavy real-time collaboration on complex documents.&lt;/p&gt;

&lt;p&gt;There's a compliance or sovereignty angle that matters. Legal practices, small financial advisors, healthcare adjacent businesses. Anything where "your data is on Google's servers" is a problem worth solving.&lt;/p&gt;

&lt;p&gt;The client wants predictable costs with no per-user licensing that grows as they hire.&lt;/p&gt;

&lt;p&gt;They're already moving to a Linux-based workstation setup, so the open source philosophy is consistent across the stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  When I don't
&lt;/h2&gt;

&lt;p&gt;If the client's workflow is deeply integrated into Google Workspace, a migration is painful and the value is lower. At Zerolock we've walked away from a couple of these rather than force a migration that would hurt the client more than help them. Heavy Docs and Sheets collaboration, Gmail filtering built into daily operations, Google Meet for all external calls: these are signals to evaluate carefully before committing to a switch.&lt;/p&gt;

&lt;p&gt;Same with shops that use Teams heavily for video and internal communication. The Nextcloud Talk feature is improving but it's not Teams, and if video collaboration is critical, that matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  The migration process in practice
&lt;/h2&gt;

&lt;p&gt;For a typical 5 to 8 person team, here's roughly how it goes.&lt;/p&gt;

&lt;p&gt;Set up the Nextcloud instance. I use Docker on a VPS, behind a reverse proxy, with automated backups to a separate location.&lt;/p&gt;

&lt;p&gt;Migrate existing files from Google Drive using rclone. It handles the OAuth flow cleanly and can do incremental syncs during the transition period.&lt;/p&gt;

&lt;p&gt;Install and configure desktop clients on each workstation. On Linux (Kubuntu in my case) this is straightforward via the official package.&lt;/p&gt;

&lt;p&gt;Train the team. Usually half a day. Most people adapt quickly because the core experience, folder, file, share, is familiar.&lt;/p&gt;

&lt;p&gt;Run parallel for two to four weeks. Both systems active, with the team gradually moving their work to Nextcloud. Then cut over.&lt;/p&gt;

&lt;p&gt;The hardest part is usually email. If they're leaving Gmail too, that's a separate migration with Thunderbird or a webmail setup. But that's a different article.&lt;/p&gt;

&lt;h2&gt;
  
  
  One thing I'd add
&lt;/h2&gt;

&lt;p&gt;I've found that clients are more comfortable with Nextcloud than they expect, mostly because it looks and behaves like a normal file system. The "everything is a proprietary document format" model confuses some users who just want to store a PDF and find it later. Nextcloud handles that interaction more naturally.&lt;/p&gt;

&lt;p&gt;It's not the right tool for every situation. But for a lot of small businesses that don't need the full cloud suite and do care about where their data lives, it's a solid, honest choice.&lt;/p&gt;

&lt;p&gt;If you're doing similar migrations and have questions about the setup, happy to compare notes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://zerolock.it" rel="noopener noreferrer"&gt;zerolock.it&lt;/a&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>nextcloud</category>
      <category>googledrive</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How we migrated an Italian logistics company from Windows to Linux: a real case study</title>
      <dc:creator>Mišo Oroz</dc:creator>
      <pubDate>Wed, 02 Sep 2026 14:34:47 +0000</pubDate>
      <link>https://dev.to/zerolock/how-we-migrated-an-italian-logistics-company-from-windows-to-linux-a-real-case-study-3gpc</link>
      <guid>https://dev.to/zerolock/how-we-migrated-an-italian-logistics-company-from-windows-to-linux-a-real-case-study-3gpc</guid>
      <description>&lt;p&gt;In early 2026 I migrated a logistics and transport company in Bergamo, Italy from a fully proprietary setup to Linux and open source. Five workstations, five employees, zero technical background on their side. Here is what we did, what happened, and what I learned.&lt;/p&gt;

&lt;h2&gt;
  
  
  The starting point
&lt;/h2&gt;

&lt;p&gt;The company was running Windows on every workstation, Microsoft Office for documents, a third-party antivirus renewed annually, and an external IT technician called in every time something broke. They had no internal IT knowledge and no interest in acquiring any. They just wanted things to work.&lt;/p&gt;

&lt;p&gt;Their main pain points were slow machines, frequent crashes after Windows updates, and a recurring bill that kept growing without any visible improvement.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we installed
&lt;/h2&gt;

&lt;p&gt;I replaced everything with a fully open source stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kubuntu as the operating system on all five workstations&lt;/li&gt;
&lt;li&gt;LibreOffice in place of Microsoft Office&lt;/li&gt;
&lt;li&gt;Thunderbird for email&lt;/li&gt;
&lt;li&gt;Nextcloud for file sharing and internal document management, self-hosted on a server in Italy&lt;/li&gt;
&lt;li&gt;Mattermost for internal team communication&lt;/li&gt;
&lt;li&gt;pfSense as the network firewall&lt;/li&gt;
&lt;li&gt;WireGuard for VPN access&lt;/li&gt;
&lt;li&gt;Automated encrypted backups running nightly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total recurring licence cost after migration: zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  The migration process
&lt;/h2&gt;

&lt;p&gt;The full migration took approximately one week. I configured everything on a test machine first, validated all workflows with the team, then rolled out workstation by workstation during off-hours to avoid interruptions.&lt;/p&gt;

&lt;p&gt;The biggest challenge was not technical. It was psychological. People are used to what they know. I spent as much time sitting with each employee and showing them that LibreOffice opens their Word files and Nextcloud works like Google Drive as I did on the actual technical setup.&lt;/p&gt;

&lt;p&gt;After three days they stopped asking about Windows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results after six months
&lt;/h2&gt;

&lt;p&gt;Six months in, the company has not called me for a single ordinary technical issue. The machines are faster. Updates do not break anything. There have been zero security incidents.&lt;/p&gt;

&lt;p&gt;The most concrete result: each employee recovered roughly one hour per day that was previously lost to slowdowns, crashes and waiting for the external technician. On five people that is five hours a day returned to productive work.&lt;/p&gt;

&lt;p&gt;Licence costs are gone. The infrastructure belongs to them, not to a vendor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters beyond one company
&lt;/h2&gt;

&lt;p&gt;Most Italian SMEs are in the same situation. They pay recurring fees for software they do not own, store data on foreign servers subject to the US Cloud Act, and depend on vendors who can change the rules at any time.&lt;/p&gt;

&lt;p&gt;Linux and open source infrastructure is not experimental. It is mature, stable, and ready for business use. The barrier is not technical. It is awareness.&lt;/p&gt;

&lt;p&gt;I wrote the Zerolock Manifesto (&lt;a href="https://zerolock.org" rel="noopener noreferrer"&gt;https://zerolock.org&lt;/a&gt;) on exactly this topic. If you are working on similar migrations or have questions about the stack, I am happy to discuss in the comments.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>opensource</category>
      <category>selfhosted</category>
      <category>privacy</category>
    </item>
  </channel>
</rss>
