Integrate Home Assistant Green into a wireguard VPN (client without port forwarding)

Introduction

Hello everybody. I recently bought my first Home Assistant Green and needed a setup for remote access with wireguard. The existing Wireguard Community Add-on did not work for me for two reasons:

  • The community add-on requires port forwarding to my HA Green device. Well, my router is behind a carrier-grade NAT and port forwarding is just too much of a hassle. So the wireguard on my HA Green device has to “dial out” in order to get through CGNAT and firewalls.

  • The community add-on provides support for a server setup only but not for a client setup. I wanted to integrate my HA Green into my existing wireguard VPN as a client, connecting to my existing wireguard server.

So I started to work on a solution. I logged into the device in order to look at the problem from the inside and managed to get it to work within the homeassistant container. I took inspiration from the Wireguard Community Add-on. At the end, I boiled everything down to the most canonical solution and wrote a new add-on.

It took me the better part of a day. So I thought I’d grab the chance to give something back to this great community and decided to share my work. This tutorial will show you how to create a minimal Home Assistant add-on to connect to an existing wireguard server.

Before we begin, please open another browser tab with this Tutorial: Making your first add-on. Our process here is very similar to the description in that tutorial, and we are not going into the details for tasks that are explained there. So when I write things like “start the samba server add-on and navigate to the addons folder”, you can look it up in the other tutorial.

So let’s start.

Building the add-on

  • Start the samba server addon and navigate to the addons folder
  • Create a new directory called runwg. Always inside that directory:
  • Create the Dockerfile
ARG BUILD_FROM
FROM $BUILD_FROM

# We need two additional packages for the
# wireguard client:

RUN apk add --no-cache iptables
RUN apk add --no-cache wireguard-tools

# Copy the wireguard configuration file(s)
# and the run script into the container.

COPY rootfs /
COPY run.sh /
RUN chmod 700 /etc/wireguard/*
RUN chmod a+x /run.sh

CMD [ "/run.sh" ]

Please note: if you are on Windows, make sure to use the LF-only line ending convention for all files described in this tutorial.

  • Create the file config.yaml
name: "Run Wireguard"
description: "Run wireguard from config files"
version: "1.0.0"
slug: "runwg"
arch:
  - aarch64
  - amd64
  - armhf
  - armv7
  - i386
init: false
privileged:
  - NET_ADMIN
  • Create the file run.sh
#!bin/sh

echo "Bringing up wireguard interface wg0 ..."
wg-quick up wg0

echo "Sleep 5 seconds ..."
sleep 5

echo "Showing status information for wireguard:"
wg show

while true; do
sleep 99999
done
  • Inside runwg, create directory tree rootfs/etc/wireguard

Now, before we create the file wg0.conf, let me describe the sample setup we are going to use:

  • The HA Green is integrated in a LAN with the address range 192.168.100.0/24 and a router
  • The HA Green has a fixed IP address of 192.168.100.100 (in my case, this is archived through a permanent address lease in the DHCP server, but any other method works as well)
  • The HA Green wireguard client is going to connect to a VPN with address range 192.168.200.0/24 and has the IP address 192.168.200.2
  • The VPN has a reachable wireguard server at server.myvpn.com:5661 (that could be a cloud instance or a device inside another LAN reachable through dynamic DNS and port forwarding, etc.)

So whenever you see those IP addresses and ranges in this tutorial, you know what you have to change in order to fit your own setup. And of course you have to replace server.myvpn.com with your own wireguard server address.

Let’s continue.

  • Create the file rootfs/etc/wireguard/wg0.conf
[Interface]
PrivateKey = ___PRIVATE_KEY_HASSIO___
Address = 192.168.200.2/24

# In order to be able to access the Home Assistant frontend and
# other addresses outside of our container, we have to masquerade
# all incoming traffic from the VPN address range.
PostUp = iptables -t nat -A POSTROUTING -s 192.168.200.0/24 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -s 192.168.200.0/24 -j MASQUERADE

[Peer]
PublicKey = ___PUBLIC_KEY_SERVER___
AllowedIPs = 192.168.200.0/24
Endpoint = server.myvpn.com:5661

# Since we are traversing NAT and firewall setups, 
# we must keep the connection alive from inside.
PersistentKeepalive = 25

Fill in the required public and private keys (there are plenty of tutorials in the web how to create a key pair for wireguard) and that’s it, the addon is ready. Now install and start your add-on.

Configuring the wireguard server

Now let’s see what we have to do in order to reach our new wireguard node from it’s peers. Let’s start with the wireguard server and two scenarios.

  • Scenario one: VPN peers can only access the Home Assistant:
[Interface]
### This is server.myvpn.com
PrivateKey = ___PRIVATE_KEY_SERVER___
Address = 192.168.200.1/24
ListenPort = 5661
### whatever else belongs to your server configuration ###

[Peer]
### Home Assistant
PublicKey = ___PUBLIC_KEY_HASSIO___
AllowedIPs = 192.168.200.2/32,192.168.100.100/32

### other peers ... ###
  • Scenario two: VPN peers can access the Home Assistant and other devices on the same LAN (like the router, etc.):
[Interface]
### This is server.myvpn.com, same as above

[Peer]
### Home Assistant
PublicKey = ___PUBLIC_KEY_HASSIO___
AllowedIPs = 192.168.200.2/32,192.168.100.0/24

### other peers ... ###

And that’s it for the server.

Configuring the other peers in the network

For every other peer on the VPN, in order to reach your HA Green, you just have to add 192.168.100.100/32 (scenario one) or 192.168.100.0/24 (scenario 2) to the Allowed IPs. One example:

[Interface]
### This is my desktop
PrivateKey = ___PRIVATE_KEY_DESKTOP___
Address = 192.168.200.10/24
### whatever else belongs to your desktop configuration ###

[Peer]
PublicKey = ___PUBLIC_KEY_SERVER___
AllowedIPs = 192.168.200.0/24, 192.168.100.0/24
Endpoint = server.myvpn.com:5661

Of course, if your peers use the server as an exit node, you don’t have to change nothing:

[Interface]
### This is my desktop, same as above

[Peer]
PublicKey = ___PUBLIC_KEY_SERVER___
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = server.myvpn.com:5661

Here, the AllowedIPs already include the Home Assistant’s IP address and range.

And that’s it, now you should be able to reach the Home Assistant from every peer in your VPN using the address http://192.168.100.100/.

Additional tasks

Debugging

If you can’t connect and want to debug your wireguard add-on, you can ssh into your device, open a bash shell inside your add-on’s container and use all the tools you are used to (just use apk if something is missing).

Reconfiguration

In order to reconfigure your wireguard interface, just navigate into the addons folder, edit the rootfs/etc/wireguard/wg0.conf file and hit the Rebuild button for the add-on.

Additional interfaces

You can run as many interfaces from this add-on as you need. Just add files wg1.conf, wg2.conf etc. to the rootfs/etc/wireguard/ directory and the corresponding commands wg-quick up wg1, wg-quick up wg2 etc. to the ´run.sh´ script. Then hit the Rebuild button for the add-on.

Final notes

This setup makes most sense if your Home Assistant is the only always-on device on it’s LAN. If there is another always-on wireguard peer on that LAN, you could just use that one as an exit node for your HA Green.

I am using this on a Home Assistant Green, but it should also work if you install hassio elsewhere, for instance on a Raspberry PI.

3 Likes

hello !
i install two WG client add-ons but both of them have same issue ( that show on the picture obove )
this is the error i got :
" sysctl -q net.ipv4.conf.all.src_valid_mark=1
sysctl: error setting key ‘net.ipv4.conf.all.src_valid_mark’: Read-only file system "

at first i try to set the value of “net.ipv4.conf.all.src_valid_mark” to 1
but i cant change it
do you have any idea of what is the problem ? and how can i solve it ?
its wasted 8 hours of my time
and i also have to say i use HA os ( that mean its directly install and run on sd card and i dont use docker version on raspbian)

The problem is easy to spot: wg-quick issues a command to run sysctl which attempts to write to a read-only file system. Looks like you are running sysctl in a PostUp directive. Are you sure you need to do that?

Anyway, I can’t help you debug your setup which is very different from the one I provide in this tutorial. But I can tell things look problematic to me, with all that routing stuff.

You could try to use my wg0.conf setup and just replace 192.168.200.* by 10.0.0.*. It should just work and does not require sysctl and all the other PostUp stuff you seem to use. Here is my sample output:

s6-rc: info: service legacy-services: starting
s6-rc: info: service legacy-services successfully started
Bringing up wireguard interface wg0 ...
[#] ip link add wg0 type wireguard
[#] wg setconf wg0 /dev/fd/63
[#] ip -4 address add 192.168.200.2/24 dev wg0
[#] ip link set mtu 1420 up dev wg0
[#] iptables -t nat -A POSTROUTING -s 192.168.200.0/24 -j MASQUERADE
Sleep 5 seconds ...
Showing status information for wireguard:
interface: wg0
  public key: Uw6K0k8o72MIV6828x+CzvjrlWWgS6qdchs3NWeKs0Y=
  private key: (hidden)
  listening port: 49535

peer: O5nbD95bilacNqrf7bD/QrPinLL+Ai+easiKZnzxnlA=
  endpoint: (redacted):5661
  allowed ips: 192.168.200.0/24
  latest handshake: 5 seconds ago
  transfer: 92 B received, 180 B sent
  persistent keepalive: every 25 seconds

PS Looks like you assign address 10.0.0.2/32 to your interface, I think that should be 10.0.0.2/24 in order to tell the router that your address 10.0.0.2 participates in a 10.0.0.* subnet with your other peers.

Hello, I set AllowedIPs to 10.0.0.2/24 in your add-ons and the problem was not solved and it still gave the same error as before.
But in another wg client, it connected to the server at IP 10.0.0.2/24
Both configs are exactly the same and there is no difference, but I have a problem with your add-ons, but it was connected easily in the wg client add-ons.
But when I connect to the Wireguard server, it seems that no traffic goes to the Wireguard server. It seems that there is a problem with the traffic routing settings. Below, I do not receive the public IP of the Wireguard server in Home Assistant.

I don’t know what other client add-ons you are using, but if they work for your use case, you should probably stick with them? Because I don’t even know what you are trying to achieve. This tutorial is about configuring a wireguard VPN so you can connect to your HA Green web frontend from any peer in than VPN. If that’s your goal, you can try to follow this tutorial. But then, instead of copying the wireguard config from that other add-ons, you should stick to the configurations provided in the tutorial just adapting the IP addresses and ranges to your own VPN and LAN ranges, for instance, 192.168.200.* to 10.0.0.* - otherwise I really can’t help you out.

Hey! This is exactly what I’m looking for, thanks for the tutorial! I have a couple remote HA installs that I want to VPN to my home network.

I’m very close to making this work… I can ping the HA VPN client from a different VPN client (my phone), but I can’t load the web interface. It looks like everything is working, but I must be missing something. Any ideas??

s6-rc: info: service s6rc-oneshot-runner: starting
s6-rc: info: service s6rc-oneshot-runner successfully started
s6-rc: info: service fix-attrs: starting
s6-rc: info: service fix-attrs successfully started
s6-rc: info: service legacy-cont-init: starting
s6-rc: info: service legacy-cont-init successfully started
s6-rc: info: service legacy-services: starting
s6-rc: info: service legacy-services successfully started
Bringing up wireguard interface wg0 …
[#] ip link add wg0 type wireguard
[#] wg setconf wg0 /dev/fd/63
[#] ip -4 address add 192.168.70.2/32 dev wg0
[#] ip link set mtu 1420 up dev wg0
[#] resolvconf -a wg0 -m 0 -x
[#] ip -4 route add 192.168.70.2/32 dev wg0
[#] ip -4 route add 192.168.70.0/24 dev wg0
[#] ip -4 route add 192.168.65.0/24 dev wg0
[#] iptables -t nat -A POSTROUTING -s 192.168.70.0/24 -j MASQUERADE
Sleep 5 seconds …
Showing status information for wireguard:
interface: wg0
public key: ********************
private key: (hidden)
listening port: 49559

peer: **********************
endpoint: ***********:51825
allowed ips: 192.168.70.2/32, 192.168.70.0/24
latest handshake: 5 seconds ago
transfer: 220 B received, 308 B sent
persistent keepalive: every 25 seconds

Hi.

I guess your HA has a fixed IP address in a different subnet? Let’s say, it’s connected to a LAN with 192.168.190.0/24 and has the number 192.168.190.99

Now you have to add AllowedIPs to your VPN server and all VPN peers that should reach your HA, like this:

AllowedIPs = 192.168.190.0/24 to reach all machines in the HA’s LAN

or

AllowedIPs = 192.168.190.99/32 to reach only the HA

Put this in your server config for the HA peer AND in your mobile phone config for the server peer (keeping the other AllowerIPs directives as well, of course).

With that, you should not need any additional route commands in your PostUp config.

Still having a hard time, I can’t connect to the remote HA instance. It’s like I’m hitting the WireGuard client docker, but can’t load the HA web UI. Thinking the issue is with the iptable settings?

10.253.3.3:8123 gives a refused to connect error, and I can ping it.
192.168.25.5:8213 just doesn’t load at all, and no ping response.

I switched from a Unifi WireGuard VPN to an Unraid one, same issue. :confused:
192.168.25.5 is the HA ip address on the remote LAN network (example IP). I’ve added 192.168.25.5/32 to the remote HA WireGuard client and the WireGuard client on my phone.

Bringing up wireguard interface wg0 …
[#] ip link add wg0 type wireguard
[#] wg setconf wg0 /dev/fd/63
[#] ip -4 address add 10.253.3.3/32 dev wg0
[#] ip link set mtu 1420 up dev wg0
[#] resolvconf -a wg0 -m 0 -x
[#] ip -4 route add 192.168.25.5/32 dev wg0
[#] ip -4 route add 10.253.3.1/32 dev wg0
[#] ip -4 route add 192.168.60.0/24 dev wg0
[#] ip -4 route add 10.253.3.0/24 dev wg0
[#] iptables -t nat -A POSTROUTING -s 10.253.3.0/24 -j MASQUERADE
Sleep 5 seconds …
Showing status information for wireguard:
interface: wg0
public key: **********
private key: (hidden)
listening port: 36923

peer: **************
endpoint: *************
allowed ips: 10.253.3.1/32, 10.253.3.0/24, 192.168.60.0/24, 192.168.25.5/32
latest handshake: 5 seconds ago
transfer: 284 B received, 372 B sent
persistent keepalive: every 25 seconds

Hello, just a heads up: I am on vacation and won’t see a computer until end of next week :slight_smile: good luck here

Thanks bwb,

I ended up finding this addon repository that has a WireGuard Client Addon, seems to work!

1 Like

Glad you solved it! This tutorial works on a very specific setup with the HA Green, and if your demands differ, I find it very difficult to help debugging because I can’t test anything, basically I have to guess what’s wrong. So if you run into problems, you probably are better off with that other add-on.

Thank you for your work, @bwb! It was easy to understand, and I got it connected to my VPN. Then I changed my VPN and thus had to change the runwg peer credentials, but I didn’t understand why my changes weren’t taking effect - I read through your post again and you had already explained that I had to reBUILD the add-on. Great!

So now the runwg add-on’s log shows that it successfully handshakes, and I can ping the runwg IP from another IP on the VPN (and vice versa if I docker exec into the runwg container). But I’m not having success actually accessing Home Assistant. I’m routing hass.mydomain.com through the vpn to the runwg IP address on port 8123 (e.g., 10.13.16.2:8123), but I get “OpenSSL/3.1.4: error:0A00010B:SSL routines::wrong version number”.

I realize that you are not tech support, and that runwg is for a specific use-case that I may be outside of, but I thought I’d ask just in case you knew what this meant.

Hi.

“through the vpn to the runwg IP address” - you should access the HA through the IP address of the HA control panel in it’s local area network, not the wireguard client’s address. You should be able to ping the Home Assistant IP address from within the runwg container, and the iptables masquerading shown in the wireguard config file should pass your traffic through just fine from any other wireguard peer that can ping the runwg peer. Just make sure the HA’s local area network address is in the AllowedIPs of those peers and the wireguard server.

The openssl error message you refer to does not seem to be wireguard related at all - maybe some problem with https.

So in order to narrow down your problem for debugging you should first try to access by IP address and without encryption (http://10.2.3.4/ - replace 10.2.3.4 with your HA address, NOT the runwg address), and after successfully connecting that way, you can try to setup SSL and DNS, but that would be out of the scope of this little project.

Note: I am in vacation and rarely looking at my mails, I don’t mind to help out but my replies may take a few days :slight_smile:

Thanks again for the assistance. I believe that my VPN is set up to only route VPN traffic - it won’t route to non-VPN IPs. However, Traefik itself can do that, so I think I can ditch the runwg add-on and create a file-based config for Traefik to route to HA directly - the traffic would still be going through my VPN, it’s just that HA wouldn’t be on the VPN. Or so my theory goes.

Enjoy your vacation!

hi, did you manage to get this addon to work? it doesn’t work for me.
it connects for a few seconds and then disconnects

it connects for a few seconds and then disconnects

this makes me think… did you make sure that all of the peers in your wireguard setup have different private keys, and there is no other peer with the same private key as the peer in the add-on?

hello, everything is correct, I created a new tunnel and configured the addon with the values ​​of the fritzbox file.
I think there is a problem in the addon, this is the log with an error