You want a personal VPN on a server you control, without hand-writing WireGuard peers or building a strongSwan certificate authority yourself. Algo VPN from Trail of Bits does both in one Ansible run, and the Algo VPN installation still works, but not untouched. On the current main branch a default setting leaves IKEv2 with no connection loaded, and a hardening line stops strongSwan on its next start. WireGuard is not affected, so the playbook still prints its green banner.
We ran the full install on Ubuntu 24.04 and 22.04 cloud servers, rebuilt each one from a clean image, and connected from an Ubuntu 26.04 laptop over WireGuard and IKEv2. This guide covers the local install, the two edits that keep IPsec alive, clients on Linux, Android and iPhone, adding users later, and why an Ubuntu 26.04 server cannot run Algo yet.
Ran this in October 2026 on Ubuntu 24.04.5 and 22.04.5 servers with Algo from the main branch (commit b144487), strongSwan 5.9.13 and WireGuard tools 1.0.20210914; both tunnels came back by themselves after a reboot.
How an Algo server fits together
Algo turns one dedicated Ubuntu server into two VPN endpoints that share one encrypted DNS resolver. WireGuard listens on UDP 51820 for Linux, Android and Windows clients, and strongSwan answers IKEv2 on UDP 500 and 4500 for the built-in VPN client on Macs and iPhones. Every client sends its DNS to a local dnscrypt-proxy, which blocks ad domains and forwards the rest to Cloudflare. Traffic leaves through NAT, so the websites you visit see the public IP of the server instead of yours.

The note in the corner is the first thing that broke for us. Algo opens the three UDP ports in its own iptables rules on the server, but the firewall of a cloud provider sits in front of that, and until it allows the same ports, a WireGuard client shows no handshake at all.
Prerequisites
Algo is built for a dedicated VPN server, because it replaces the firewall rules and DNS settings on the machine it touches and has no uninstall option. Use a fresh instance for it rather than a box that already runs a website or a database:
- Ubuntu 24.04 or 22.04 server, fresh install
- 1 vCPU and 1 to 2 GB RAM (ours had 1 vCPU, 2 GB)
- A public IPv4 address, either on the NIC or a cloud floating IP
- SSH access as a sudo user, with key login
- UDP 51820, 500 and 4500 reachable from the internet
Sizing barely matters for a personal VPN with a handful of devices, since WireGuard and strongSwan both run in the crypto path of the kernel and the server mostly forwards packets. The Algo documentation lists Ubuntu 22.04 as the supported release, and 24.04 worked the same way in our test. Prefer 24.04 for a new server, because standard support for 22.04 ends in May 2027. If you have not set up key login yet, follow the SSH key authentication guide first; Algo keeps TCP 22 open but drops almost everything else.
Step 1: Open the VPN ports in the cloud firewall
Open UDP 51820, 500 and 4500 in the security group or firewall of your provider before you run Algo, because the playbook cannot reach that layer. Our first server sat on OpenStack with a security group that allowed only SSH, and the WireGuard client brought its interface up, routed everything into it and then sat there with no “latest handshake” line. A packet capture on the server showed zero packets arriving on UDP 51820.
On OpenStack, the rules look like this (AWS, Hetzner and DigitalOcean have the same three entries in their web consoles):
openstack security group create algo-vpn
openstack security group rule create --ingress --protocol udp --dst-port 51820 algo-vpn
openstack security group rule create --ingress --protocol udp --dst-port 500 algo-vpn
openstack security group rule create --ingress --protocol udp --dst-port 4500 algo-vpn
openstack server add security group algo-srv24 algo-vpn
Replace algo-srv24 with the name or ID of your own instance. Port 51820 carries WireGuard, while IKEv2 needs both 500 for the key exchange and 4500 for NAT traversal, which almost every phone on a mobile network ends up using.
Step 2: Update the server and install uv
Start from a fully patched system and reboot if the kernel was updated, so Algo configures the kernel you will actually run.
sudo apt update && sudo apt full-upgrade -y
[ -f /var/run/reboot-required ] && sudo reboot
The ./algo launcher runs everything through uv, which builds the Python environment with Ansible on the first run. Ubuntu does not package uv, so the launcher stops and asks you to pick pipx, snap or a download script. Installing it from snap beforehand skips that menu, and sudo on Ubuntu already has /snap/bin in its path:
sudo snap install astral-uv --classic
The snap installs a current uv build from the community snap that the Algo launcher itself suggests:
astral-uv 0.12.21 from Alex Lowe (lengau*) installed
Nothing else needs installing by hand, because the first ./algo run fetches Python packages and Ansible into a local .venv inside the repository.
Step 3: Clone Algo and list your users
Clone the repository into your home directory. Algo writes every client profile under configs/ inside this folder, so keep it after the install because you need it again to add users:
git clone https://github.com/trailofbits/algo.git
cd ~/algo
Each entry in the users list becomes one device with its own keys, so a phone and a laptop belonging to the same person still get two entries. Open the config file:
vim config.cfg
Edit the users block near the top. We used these three names:
users:
- phone
- laptop
- kibet-desktop
The rest of the file has sensible defaults, and the comment above them warns that most are hard to change once the server is deployed. The ones worth reading before you run anything:
dns_encryption: truesends DNS through dnscrypt-proxyBetweenClients_DROP: truestops VPN clients reaching each otherwireguard_port: 51820for networks that block the default portadblock_listswith the StevenBlack hosts list as the default
If your VPN devices need to reach each other (a laptop on the road opening a share on your desktop, both connected to Algo), set BetweenClients_DROP to false now, since the rule is written into the firewall at deploy time. Reaching your home LAN is a different setup: Algo runs on a box inside that LAN and your router forwards the three UDP ports to it.
Step 4: Apply the two IPsec fixes before deploying
Make two edits before the first run, or IKEv2 will not work even though Algo reports success. Both are described in the open upstream issue trailofbits/algo#15048, and we reproduced both on a stock Ubuntu 24.04 deploy. The first is a setting that configures WireGuard, strongSwan and DNS in parallel. It defaults to on even though config.cfg shows it commented out as false, and the parallel run starts strongSwan before its certificate and connection files exist, so ipsec statusall listed an empty “Connections:” section.
Turn the parallel service setup off by uncommenting that line:
sed -i 's/^# performance_parallel_services: false/performance_parallel_services: false/' config.cfg
The second is a systemd hardening line, ReadOnlyPaths=/proc/net/pfkey, in the drop-in Algo writes for strongSwan. The path resolves through /proc/self, so systemd tries to mount a per-process path that does not exist yet. The unit then dies with 226/NAMESPACE on every start after the first one. strongSwan uses the kernel-netlink plugin and never touches pfkey, so removing the line from the template costs nothing.
sed -i '/^ReadOnlyPaths=\/proc\/net\/pfkey$/d' roles/strongswan/templates/100-CustomLimitations.conf.j2
Check that exactly those two files changed:
git diff --stat
Your user edits show up in config.cfg as well, which is why it has more than one changed line:
config.cfg | 4 ++--
roles/strongswan/templates/100-CustomLimitations.conf.j2 | 1 -
2 files changed, 2 insertions(+), 3 deletions(-)
If a later Algo commit fixes issue 15048, the sed commands simply match nothing, so they are safe to run either way.
Step 5: Run the Algo installer
Check which port your SSH daemon listens on before you start (sudo ss -tlnp | grep sshd). The firewall Algo installs on a local deploy accepts SSH only on the port Ansible connected through, which is 22 here, so a server whose sshd listens somewhere else loses SSH the moment the rules load. If that happens, the web console of your provider is the way back in.
Run the launcher with sudo, which a local install requires because Ansible configures the same machine it runs on. The first run spends about a minute building the Python environment and installing Ansible collections before the first question appears:
sudo ./algo
The first prompt picks where Algo deploys. Options 1 to 11 create a new cloud server through the API of that provider, while option 12 installs onto the machine you are on:
[Cloud prompt]
What provider would you like to use?
1. DigitalOcean
2. Amazon Lightsail
3. Amazon EC2
4. Microsoft Azure
5. Google Compute Engine
6. Hetzner Cloud
7. Vultr
8. Scaleway
9. OpenStack (DreamCompute optimised)
10. CloudStack
11. Linode
12. Install to existing Ubuntu latest LTS server (for more advanced users)
Enter the number of your desired provider
:
These are the answers we gave on every server. Pressing Enter takes the default shown in brackets:
| Prompt | Answer | Why |
|---|---|---|
| What provider would you like to use? | 12 | Local install on this server |
| Connect On Demand on cellular networks | Enter (N) | Only affects the Apple profiles |
| Connect On Demand on Wi-Fi | Enter (N) | Same, Apple profiles only |
| Do you want to retain the keys (PKI)? | y | Required to add users later |
| Do you want to enable DNS ad blocking? | y | Blocks ad domains for every client |
| Own account for SSH tunneling? | Enter (N) | Not needed with WireGuard |
| Type ‘yes’ to confirm… | yes | Accepts the firewall takeover |
| IP address of your server | Enter (localhost) | Ansible targets this machine |
| Public IP address or domain name | 203.0.113.10 | Goes into the certificates and profiles |
Answer y to retaining the PKI unless you are sure the user list will never change. Without the CA key, adding a phone next month means deploying a new server and handing out new profiles to everyone. The confirmation step prints a warning that is worth reading once, because it describes exactly what happens to the server:
======================================================================
*** DESTRUCTIVE OPERATION ***
======================================================================
Algo is designed for DEDICATED VPN servers only.
This installation will:
- Replace all firewall rules (blocks most incoming traffic)
- Modify system network and DNS settings
- Install and configure VPN services
THERE IS NO UNINSTALL OPTION.
For the last prompt, enter the address clients will connect to. Behind a floating IP or an AWS elastic IP, that is the public address and not the private one on the NIC. Algo writes it into the server certificate, and IKEv2 clients refuse a certificate whose name does not match. The playbook then ran for about two and a half minutes on our 1 vCPU servers and ended like this (the passwords are masked here):
"\"# Congratulations! #\"",
"\"# Your Algo server is running. #\"",
"\"# Config files and certificates are in the ./configs/ directory. #\"",
"\"# Go to https://whoer.net/ after connecting #\"",
"\"# and ensure that all your traffic passes through the VPN. #\"",
"\"# Local DNS resolver 172.31.150.77 #\"",
""
],
" \"# The p12 and SSH keys password for new users is XXXXXXXXX #\"\n",
" \"# The CA key password is XXXXXXXXXXXXXXXX #\"\n",
...
localhost : ok=206 changed=97 unreachable=0 failed=0 skipped=145 rescued=0 ignored=0
Save the CA key password in your password manager now, since update-users asks for it and Algo does not print it again. Lost it? Then the CA key cannot sign new certificates, and adding a device means a fresh deploy. The local DNS resolver address is random per server, so yours will differ from 172.31.150.77.
Step 6: Verify WireGuard, IKEv2 and DNS on the server
A green play recap does not prove IKEv2 works, as the stock run showed us, so check the services directly. Reboot first, because the strongSwan bug from Step 4 only shows up on the second start of the unit:
sudo reboot
Once you are back in, all three services should report active:
systemctl is-active wg-quick@wg0 strongswan-starter dnscrypt-proxy
Then confirm strongSwan loaded its connection and the CA, which is exactly what the stock deploy was missing:
sudo ipsec statusall | sed -n '/Connections:/,/Security/p'
A working server lists the ikev2-pubkey connection with the certificate named after your public IP:
Connections:
ikev2-pubkey: %any...%any IKEv2, dpddelay=35s
ikev2-pubkey: local: [203.0.113.10] uses public key authentication
ikev2-pubkey: cert: "CN=203.0.113.10"
ikev2-pubkey: remote: uses public key authentication
ikev2-pubkey: child: 0.0.0.0/0 ::/0 === dynamic TUNNEL, dpdaction=none
Security Associations (0 up, 0 connecting):
Zero security associations is normal while no client is connected. Our server after the reboot, with all three services up, one IKEv2 connection loaded and a WireGuard peer for each of the three users:

The listening sockets confirm the ports from the diagram. Port 51820 shows no process name because WireGuard runs in the kernel, while 500 and 4500 belong to the strongSwan charon daemon.
sudo ss -ulnp | grep -E ':(500|4500|51820)\s'
Each port appears twice, once for IPv4 and once for IPv6:
UNCONN 0 0 0.0.0.0:4500 0.0.0.0:* users:(("charon",pid=745,fd=15))
UNCONN 0 0 0.0.0.0:500 0.0.0.0:* users:(("charon",pid=745,fd=14))
UNCONN 0 0 0.0.0.0:51820 0.0.0.0:*
UNCONN 0 0 [::]:4500 [::]:* users:(("charon",pid=745,fd=13))
UNCONN 0 0 [::]:500 [::]:* users:(("charon",pid=745,fd=12))
UNCONN 0 0 [::]:51820 [::]:*
The firewall Algo installed sets the INPUT policy to DROP and accepts little besides SSH, the three VPN ports, IPsec traffic (ESP and AH), rate-limited ping and DNS from the two tunnel subnets to the local resolver. That is why the server must be dedicated: a web server on 443 would be unreachable from that moment on.
Step 7: Copy the client profiles off the server
Algo ran as root, so every file under configs/ is owned by root with mode 600, and a plain scp from your laptop fails with “Permission denied”. Do not fix that with a recursive chown on configs/, because update-users later refuses to run when the folder owner does not match the user running it. Copy only the client profiles into a separate folder instead. On a local install Algo also creates a configs/localhost link to the folder named after your IP, which saves typing the address:
mkdir -p ~/vpn-profiles
sudo cp ~/algo/configs/localhost/wireguard/*.conf ~/algo/configs/localhost/wireguard/*.png ~/vpn-profiles/
sudo chown -R $USER:$USER ~/vpn-profiles
Each user gets a .conf file for desktop clients and a .png QR code for phones:
ls ~/vpn-profiles
You should see a pair of files per user from config.cfg:
kibet-desktop.conf kibet-desktop.png laptop.conf laptop.png phone.conf phone.png
Copying the whole wireguard folder looks simpler but drags along a hidden .pki directory with the private key of every peer and of the server itself, which should never leave the server. Treat each .conf like a password too, because it contains the private key of that device and anyone holding it can use your VPN.
Step 8: Connect a Linux client with WireGuard
WireGuard is the protocol to use on Linux, since it needs only the WireGuard tools and one file. Pull your profile down from the server on the laptop, using your own SSH user and server address:
scp [email protected]:vpn-profiles/kibet-desktop.conf .
The profile Algo generated routes all IPv4 and IPv6 traffic into the tunnel and points DNS at the resolver on the server (keys removed here):
[Interface]
PrivateKey = <redacted>
Address = 10.49.0.4
DNS = 172.31.150.77
MTU = 1370
[Peer]
PublicKey = zJ7xy+icjSCeYRX3ED1nh0+yrcI1tDDhATHoAZ8WtW8=
PresharedKey = <redacted>
AllowedIPs = 0.0.0.0/0,::/0
Endpoint = 203.0.113.10:51820
Install the tools, place the profile where wg-quick looks for it, and bring the tunnel up. The file name becomes the interface name, so algo.conf gives you an interface called algo:
sudo apt install -y wireguard-tools
sudo install -m 600 kibet-desktop.conf /etc/wireguard/algo.conf
sudo wg-quick up algo
Check the result from the client. Before the tunnel, our laptop showed its home IP, and afterwards it showed the address of the server. The resolver was the one Algo pushed, and a known ad domain came back blocked.

You can run the same checks yourself:
curl -s -4 https://ifconfig.me; echo
resolvectl dns algo
sudo wg show algo | grep handshake
A latest handshake line a few seconds old means the server answered. If that line is missing, go back to Step 1, because the cloud firewall is the usual cause. To bring the tunnel up at every boot, enable the systemd unit that ships with the WireGuard tools:
sudo systemctl enable --now wg-quick@algo
Use sudo wg-quick down algo, or sudo systemctl disable --now wg-quick@algo if you enabled it, to go back to your normal connection. The client side has no trouble on Ubuntu 26.04; our laptop ran 26.04 throughout. The manual WireGuard setup guide explains the wg-quick routing rules if you want to split-tunnel only some subnets.
Step 9: Connect Android, iPhone and Mac
Phones use the same WireGuard profiles, and the QR code saves you from moving a file onto the device. Install the official WireGuard app from Google Play or the App Store, tap the plus button, choose to scan a QR code, and point the camera at the PNG for that user from ~/vpn-profiles opened on your computer screen. Give each phone its own user, because two devices sharing one profile knock each other off the tunnel.
Use the WireGuard app on iPhones and Macs as well, unless you have a reason to avoid installing an app. The QR code works there too. Algo also writes WireGuard Apple profiles under wireguard/apple/ios and wireguard/apple/macos, owned by root like everything else, so copy one out with sudo cp and chown the same way as the IKEv2 profile below if you prefer a profile file. The built-in IKEv2 client is the alternative: we tested the IKEv2 server side with a Linux client in the next step, not with an Apple device, and an open upstream report says the IKEv2 profiles fail on some Macs and iPhones. Its suggested workaround, restarting strongSwan right after the deploy, treats the same missing connection that the Step 4 edits prevent, so a server built with them has a better chance. The IKEv2 profile folder is readable only by root, so copy one file per user:
sudo cp ~/algo/configs/localhost/ipsec/apple/phone.mobileconfig ~/vpn-profiles/
sudo chown $USER:$USER ~/vpn-profiles/phone.mobileconfig
AirDrop the file to the device. On an iPhone the install prompt appears as soon as the AirDrop is accepted; on a Mac, double-click the file and approve it under System Settings > General > Device Management (Privacy & Security > Profiles on Ventura), then connect from System Settings > VPN (Network on releases before Sequoia), following the Algo Apple client notes. The Connect On Demand answers from Step 5 only change these Apple profiles.
Step 10: Connect a Linux client over IKEv2 (optional)
You only need this section if WireGuard is blocked where you work, since some networks pass IPsec but drop unknown UDP ports. It needs the strongSwan “starter” package, which still provides the classic ipsec command that the Algo client files are written for. On Ubuntu 26.04 the plain strongswan package no longer pulls it in, so name it explicitly:
sudo apt install -y strongswan-starter libstrongswan-standard-plugins
The client needs five files for its user: the certificate and key from the hidden .pki folder, plus the CA certificate, connection snippet and secrets line from manual/. Copy them out on the server with absolute paths, because the ipsec folder is readable only by root and a plain cd into it fails with “Permission denied”:
mkdir -p ~/vpn-profiles/ipsec
D=~/algo/configs/localhost/ipsec
sudo cp $D/.pki/certs/kibet-desktop.crt $D/.pki/private/kibet-desktop.key $D/manual/cacert.pem $D/manual/kibet-desktop.conf $D/manual/kibet-desktop.secrets ~/vpn-profiles/ipsec/
sudo chown -R $USER:$USER ~/vpn-profiles/ipsec
On the client, pull the folder down with your own SSH user and server address:
scp -r [email protected]:vpn-profiles/ipsec .
Set the server address in a variable, since the connection name contains it, then install each file where strongSwan expects it:
export ALGO_IP=203.0.113.10
cd ipsec
sudo install -m 644 kibet-desktop.crt /etc/ipsec.d/certs/
sudo install -m 600 kibet-desktop.key /etc/ipsec.d/private/
sudo install -m 644 cacert.pem /etc/ipsec.d/cacerts/
cat kibet-desktop.secrets | sudo tee -a /etc/ipsec.secrets
cat kibet-desktop.conf | sudo tee -a /etc/ipsec.conf
The connection Algo generated sends everything (0.0.0.0/0) through the tunnel, including traffic to your own LAN. The Algo client docs add a passthrough connection so the printer, the NAS and any SSH session from the LAN stay local; replace 192.168.1.0/24 with your subnet:
printf "\nconn lan-passthrough\n leftsubnet=192.168.1.0/24\n rightsubnet=192.168.1.0/24\n authby=never\n type=pass\n auto=route\n" | sudo tee -a /etc/ipsec.conf
One more change matters on Ubuntu 24.04 and newer clients, where systemd-resolved manages DNS. By default strongSwan hands the pushed DNS server to resolvconf on the loopback interface, systemd-resolved refuses it, and your queries keep going to the LAN router outside the tunnel, which also skips the Algo ad blocking. Our first connection printed exactly that:
installing DNS server 172.31.150.77 via resolvconf
resolvconf: Dropped protocol specifier '.ipsec' from 'lo.ipsec'. Using 'lo' (ifindex=1).
resolvconf: Failed to set DNS configuration: Link lo is loopback device.
adding DNS server failed
handling INTERNAL_IP4_DNS attribute failed
Point the plugin at your real network interface instead. The iface option exists in the strongSwan builds of Ubuntu 24.04 and 26.04; the older build on 22.04 names it differently, so this sed matches nothing there. The first command finds the interface that holds the default route:
IFACE=$(ip route show default | awk '{print $5; exit}')
sudo sed -i "s|^ # iface = lo.ipsec| iface = ${IFACE}|" /etc/strongswan.d/charon/resolve.conf
grep -n "iface =" /etc/strongswan.d/charon/resolve.conf
Restart strongSwan and bring the connection up. Its name is algovpn- followed by the server address:
sudo ipsec restart
sudo ipsec up algovpn-${ALGO_IP}
The last lines show the AES-GCM-256 proposal Algo enforces, the virtual IP from the 10.48.0.0/16 pool and the DNS server going onto the real interface:
installing DNS server 172.31.150.77 via resolvconf
installing new virtual IP 10.48.0.1
peer supports MOBIKE
IKE_SA algovpn-203.0.113.10[2] established between 192.168.1.50[CN=kibet-desktop]...203.0.113.10[203.0.113.10]
selected proposal: ESP:AES_GCM_16_256/NO_EXT_SEQ
CHILD_SA algovpn-203.0.113.10{2} established with SPIs c41f63c0_i c3a14914_o and TS 10.48.0.1/32 === 0.0.0.0/0
connection 'algovpn-203.0.113.10' established successfully
With the tunnel up, resolvectl dns eth0 showed 172.31.150.77 and ad domains failed to resolve. After sudo ipsec down algovpn-${ALGO_IP}, the LAN resolver came back within two seconds; if you check immediately, you may still see the Algo address for a moment.
Step 11: Add users after deployment
Adding a device means adding a name to config.cfg and running update-users, which only works because you kept the PKI in Step 5. Existing profiles keep working; after we added a fourth user, the laptop reconnected with its old file on the first try. Add the new name under users:
cd ~/algo
vim config.cfg
Our list after adding a tablet:
users:
- phone
- laptop
- kibet-desktop
- tablet
On the current main branch, sudo ./algo update-users fails within ten seconds on a local install with 'ansible_user_id' is undefined. The first play skips fact gathering but then compares the owner of configs/ with that fact. Passing the value yourself lets the check run as intended, and with sudo the user is root:
sudo ./algo update-users -e ansible_user_id=root
It asks which server to update (answer 1 for localhost) and for the CA key password from Step 5. Our run took 1 minute 20 seconds and finished with a new peer on wg0 and a tablet.conf and tablet.png in the WireGuard folder. Copy the new files out the same way as in Step 7.
What broke in our lab
Every error below came from the test servers, either on the stock main branch or before we learned the steps above. The exact messages are here so you can match them in your own logs, and they fall into three groups:
- Two upstream bugs that break IKEv2
- An ownership guard in
update-users - A cloud firewall that dropped UDP
Only the first group needs changes to Algo itself, and Step 4 already makes them.
“Connections:” is empty after a successful deploy
On the stock deploy, the play recap showed failed=0, WireGuard worked, and sudo ipsec statusall printed an empty “Connections:” section with no CA certificate loaded. The files existed on disk, written two minutes after charon had started, so the daemon was simply never told about them. That is the parallel services setting from Step 4. Algo has no uninstall, so we rebuilt the server from a clean image and deployed again with both Step 4 edits in place, which is the cleanest fix.
strongswan-starter.service: Failed to set up mount namespacing: /proc/7616/net/pfkey
Restarting strongSwan on that stock server, the obvious fix for the empty connection list, turned a half-working IKEv2 setup into a dead one. The unit exited with status=226/NAMESPACE. systemd applies the drop-in on every start, including the one at boot, so a stock server should be expected to lose IKEv2 the first time it restarts while WireGuard carries on. Removing the ReadOnlyPaths=/proc/net/pfkey line in Step 4 prevents it.
PERMISSION MISMATCH DETECTED during update-users
We had run a recursive chown on configs/ so we could scp profiles out, and update-users then refused to run with “Config directory owner: kibet, Current user: root”. The guard is right, because new files would land with mixed ownership. The fix printed by the playbook is sudo chown -R root configs/localhost/, and Step 7 avoids the problem by copying profiles into a separate folder instead.
WireGuard interface comes up but no handshake
wg-quick up printed every routing step without an error, yet wg show had no “latest handshake” line. All traffic stalled, because the client had already moved its default route into the tunnel. sudo tcpdump -ni ens3 udp port 51820 on the server captured nothing, which points at a firewall in front of the server rather than at Algo. Opening the ports in the cloud firewall (Step 1) fixed it immediately.
Can Algo run on Ubuntu 26.04 yet?
No, not as a server. We ran the same install on a fresh Ubuntu 26.04 VM and it failed at the WireGuard step, with two more services broken by then. Each failure has a different cause, and none of them is a quick setting:
- WireGuard:
/usr/bin/wg-quick: line 32: /usr/bin/ip: Operation not permitted - dnscrypt-proxy:
[FATAL] Unsupported key in configuration file: [fallback_resolver] - strongSwan: no
strongswan-starterunit and noipseccommand installed
The WireGuard failure comes from a new AppArmor profile for wg-quick in the AppArmor package of Ubuntu 26.04. It moves ip and wg into their own profiles when wg-quick runs them. The systemd drop-in from Algo sets NoNewPrivileges=yes, under which the kernel refuses those profile changes, and the audit log shows apparmor="DENIED" operation="exec" info="no new privs" profile="wg-quick". The dnscrypt-proxy release in 26.04 dropped the fallback_resolver key that the Algo template still writes. strongSwan moved to the 6.0 series, where the plain package installs the swanctl tooling instead of the starter that Algo drives, and moving Algo to swanctl is still an open upstream task.
Run the server on 24.04 for now, which has standard support until 2029, and keep 26.04 for your clients, where WireGuard and the strongSwan starter package both worked in our tests. If you specifically want an OpenVPN server or a 26.04 host, the OpenVPN server guide covers that route, and a self-hosted Headscale mesh suits a group of machines better than a single exit server. On the road, a travel router that speaks WireGuard can load one of your Algo profiles and cover every device behind it.