惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
CXSECURITY Database RSS Feed - CXSecurity.com
WordPress大学
WordPress大学
美团技术团队
Last Week in AI
Last Week in AI
Microsoft Azure Blog
Microsoft Azure Blog
D
DataBreaches.Net
Hugging Face - Blog
Hugging Face - Blog
腾讯CDC
B
Blog RSS Feed
J
Java Code Geeks
The GitHub Blog
The GitHub Blog
人人都是产品经理
人人都是产品经理
Recorded Future
Recorded Future
The Register - Security
The Register - Security
博客园 - 三生石上(FineUI控件)
Recent Announcements
Recent Announcements
Recent Commits to openclaw:main
Recent Commits to openclaw:main
V
Vulnerabilities – Threatpost
C
Cybersecurity and Infrastructure Security Agency CISA
T
The Exploit Database - CXSecurity.com
博客园 - 【当耐特】
C
Check Point Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
Simon Willison's Weblog
Simon Willison's Weblog
T
Troy Hunt's Blog
P
Privacy International News Feed
S
Security Affairs
The Cloudflare Blog
P
Proofpoint News Feed
G
GRAHAM CLULEY
Engineering at Meta
Engineering at Meta
A
Arctic Wolf
博客园_首页
Blog — PlanetScale
Blog — PlanetScale
D
Docker
S
SegmentFault 最新的问题
N
News | PayPal Newsroom
Cyberwarzone
Cyberwarzone
H
Help Net Security
T
Threatpost
A
About on SuperTechFans
L
LINUX DO - 热门话题
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
N
News and Events Feed by Topic
Attack and Defense Labs
Attack and Defense Labs
M
MIT News - Artificial intelligence
Schneier on Security
Schneier on Security
月光博客
月光博客
F
Fortinet All Blogs
L
Lohrmann on Cybersecurity

Tomas Vondra

Some more thoughts on random_page_cost How are committers selected? The real cost of random I/O The AI inversion Stabilizing Benchmarks Don't give Postgres too much memory (even on busy systems) Qubes OS is pretty great Don't give Postgres too much memory Tuning AIO in PostgreSQL 18 Using JWT to establish a trusted context for RLS Fun and weirdness with SSDs So why don't we pick the optimal query plan? How often is the query plan optimal? Benchmarking is hard, sometimes ... Advanced Patch Feedback Session (APFS) at pgconf.dev 2025 Good time to test io_method (for Postgres 18) [PATCH IDEA] adaptive execution for `IN` queries 15 years of Prague PostgreSQL Developer Day Performance archaeology: OLAP Performance archaeology: OLTP Tuning the glibc memory allocator (for Postgres) [PATCH IDEA] parallel pgbench -i Playing with BOLT and Postgres [PATCH IDEA] amcheck support for BRIN indexes Writing a good talk proposal [PATCH IDEA] Statistics for the file descriptor cache Office hours experiment [PATCH IDEA] Using COPY for postgres_fdw INSERT batching Importing Postgres mailing list archives How to pick the first patch? Will Postgres development rely on mailing lists forever? The state of the Postgres community
Wireguard to access a home network
2025-11-17 · via Tomas Vondra

I work remotely from home, and over the years I’ve amassed a bunch of machines related to that (development, testing, benchmarking, …), and other devices you may usually find at home (printer, NAS, …). Occasionally I need remote access, and for a while SSH tunnels were good enough. I decided to simplify and clean this up and use a proper VPN wireguard. This blog post explains the setup I used.

None of this is somehow novel or invented by me. I had an idea of what I wanted to achieve, but I’m not a networking guy. Start talking about tunnels, forwarding, NAT, masquerade, … and I get confused quick. If you’re doing this for a living it’s probably obvious, and I’m sure it’s somewhere deep in the wireguard docs. But it took me a while to make this work the way I wanted.

So here’s how I did it, maybe it will help someone.

Of course, I started by searching the web, looking for how others did this. But I found nothing very useful. The results are either ancient (before wireguard), discuss one tiny piece (as you do on stackoverflow). Or it’s a dubious response generated by AI, with various inaccuracies and no way to correct them. If this is the future, I don’t like it …

Here’s a diagram of what I wanted to achieve: I have a home network on 192.168.0.1/24, behind a router, and I want to make this accessible from the outside. A machine with internet access (labeled as “client”) should be able to access IPs on the home network.

A straightforward way is to get a public IP for the router, and forward a port to some kind of gateway (SSH, VPN, …) inside. I chose not to do this, because I absolutely hate the ISP-provided modem. The less I have to interact with it, the better for my mental health. And I have other reasons for the external gateway anyway, so I chose to use it.

I’m going to use two private subnets (the exact nets don’t matter):

  • 192.168.1.0/24 - home network (regular ethernet)
  • 172.31.0.0/24 - wireguard subnet (VPN)

The goal is to allow the client to access addresses on the internal network, e.g. by doing

The idea is that we set up three wireguard hosts, forwarding traffic to the internal network:

  1. external gateway (173.31.0.1) - This is the wireguard “server” on the public internet. Can be a small VPS somewhere. Clients will connect to it, and it’ll forward traffic to the internal one.

  2. internal gateway (172.31.0.2) - This is the wireguard “server” on the home network. I’m running this on an old Raspberry Pi 4, which is more than enough. It forwards traffic from the external gateway to the actual internal hosts.

  3. client (172.31.0.3) - A client on the public internet (e.g. my laptop while I travel) connects to the external gateway, and routes all traffic to internal IPs through it.

Wireguard has nothing like “server” or “client” (or “gateway”), everything is a “peer.” I use server/gateway for hosts accepting connections from many other hosts, and client for hosts that connect to a single host at a time.

Installing wireguard

Before we start setting up the peers, you need to install wireguard packages, and related tools (e.g. wg-quick). I’m using Debian, so this does the trick for me:

You may need to use a different command depending on the system, see the wireguard docs.

Setting up peers

We’ll set up the hosts in the order listed above, i.e. external gateway, internal gateway and client. We’ll need to set up the wireguard peers, routing and IP forwarding.

To setup the tunnels we’ll need three sets of key pairs, so let’s generate them:

for k in 1 2 3; do
    wg genkey | tee privatekey | wg pubkey > publickey
    echo "KEY $k" $(cat privatekey) $(cat publickey)
done

which on my machine generated this:

KEY 1 kC+mnBYjGXL/Gm/6NGb4p6o60u0jzbo91Ou+VudaiFw= zhyz6Z3H2eblr5xVXsaaQlsEQjwnvOXSRDWEKkeHmD4=
KEY 2 8HpdppOcIv/hc4fFzx+NW9JZ8vAwxU2ZVKI7xg4Hdk0= ccSDfbZ4dAzR8TnRKc67nCqvRQoCdmpTsaP6mHlFdQo=
KEY 3 GJtyR3aqpfh4zwUJvfBStLEcWHxb9ymng+Aq2SbfW2o= Ns2YZ9hj9ahoecR9BU73gDRPHI1Kacr3iB4AqN1bWzU=

We’ll use these keys in the following sections.

External gateway

This is the “main” server, accepting tunnels opened by either the internal gateway or clients. First, configure the wg0 interface. Create file /etc/wireguard/wg0.conf with this content:

# external gateway
[Interface]
Address = 172.31.0.1/32
ListenPort = 1194
PrivateKey = kC+mnBYjGXL/Gm/6NGb4p6o60u0jzbo91Ou+VudaiFw=

# internal gateway
[Peer]
PublicKey = ccSDfbZ4dAzR8TnRKc67nCqvRQoCdmpTsaP6mHlFdQo=
AllowedIPs = 172.31.0.2/32, 192.168.1.0/24

# client
[Peer]
PublicKey = Ns2YZ9hj9ahoecR9BU73gDRPHI1Kacr3iB4AqN1bWzU=
AllowedIPs = 172.31.0.3/32

And then define / enable the wg0 interface in systemd, so that it’s started automatically.

# systemctl enable wg-quick@wg0
# systemctl start wg-quick@wg0

This is a minimal config. You may customize this, if needed.

The important bit is the AllowedIPs for the internal gateway. This tells wireguard where to route traffic for the home network (received e.g. from the client peer). Wireguard will do this automatically, you don’t need to set any additional routes.

You need to enable IP forwarding, though. The easiest way to do that is from a command line:

$ sudo sysctl -w net.ipv4.ip_forward=1

You probably want to persist that, so that it happens after every boot. Create file /etc/sysctl.d/enable-ip-forward.conf with this content

Internal gateway

The internal gateway is next. Create the wg0 interface, with this definition (189.170.14.87 is the public IP of the external gateway, assigned to the VPS/VM)

[Interface]
Address = 172.31.0.2/24
ListenPort = 1194
PrivateKey = 8HpdppOcIv/hc4fFzx+NW9JZ8vAwxU2ZVKI7xg4Hdk0=

# public gateway
[Peer]
PublicKey = zhyz6Z3H2eblr5xVXsaaQlsEQjwnvOXSRDWEKkeHmD4=
Endpoint = 189.170.14.87:1194
AllowedIPs = 172.31.0.1/24

Then enable the wg0 interface using systemctl, just like for the external gateway. The host needs IP forwarding too, so enable that too (through sysctl).

There’s one more thing this needs to do - NAT between the two networks. This translates IP addresses, so that hosts in the home network see the traffic as coming from 192.168.1.0/24 (and not from the wireguard subnet).

With nftables, you can do this by adding this to /etc/nftables.conf and restarting the nftables service:

table ip nat {
	chain postrouting {
		type nat hook postrouting priority srcnat; policy accept;
		ip saddr 172.31.0.0/24 oif "eth0" masquerade
	}
}

You may need to adjust the interface name, connected to the home network. In my case it’s eth0.

Client

Finally, let’s configure the client. This host needs only the wg0 part, nothing else (without the forwarding and NAT). Define the wg0 like this:

# client
[Interface]
Address = 172.31.0.3/24
ListenPort = 1194
PrivateKey = GJtyR3aqpfh4zwUJvfBStLEcWHxb9ymng+Aq2SbfW2o=

# external gateway
[Peer]
PublicKey = zhyz6Z3H2eblr5xVXsaaQlsEQjwnvOXSRDWEKkeHmD4=
Endpoint = 189.170.14.87:1194
AllowedIPs = 172.31.0.1/24, 192.168.1.0/24

And enable/start the wg-quick@wg0 interface using systemctl.

Notice the AllowedIPs, which tells wireguard to route 192.168.1.0/24 through the wireguard tunnel.

Summary

At this point you should be able to ping the home network from the wireguard client:

$ ping 192.168.1.1
PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
64 bytes from 192.168.1.1: icmp_seq=1 ttl=61 time=44.1 ms
64 bytes from 192.168.1.1: icmp_seq=2 ttl=61 time=44.6 ms
64 bytes from 192.168.1.1: icmp_seq=3 ttl=61 time=42.6 ms
64 bytes from 192.168.1.1: icmp_seq=4 ttl=61 time=41.5 ms

We had to do these steps:

  • external gateway
    • setup wg0 on interface (with peers for internal gateway/client)
    • enable IP forwarding
  • internal gateway
    • setup wg0 on interface (with a peer for external gateway)
    • enable IP forwarding
    • enable NAT (masquerade)
  • client
    • setup wg0 on interface (with a peer for external gateway)

I hope it was useful. It was not meant to be a complete tutorial, I’m sure there’s a lot of details you could do differently. Feel free to experiment and customize.

Also, I haven’t discussed security at all. For example, it’s obvious that if someone gains shell access to the external gateway, he/she will be able to access hosts on the private network too. So be careful and apply appropriate security practices.

Do you have feedback on this post? Please reach out by e-mail to tomas@vondra.me.