Install Cloudflare WARP on any Linux Distro, Thanks to Distrobox!
Tags: tutorial, linux, productivity, network
Published: 2024-04-15 03:37:40

> **Update 05/19/24:** Using a normal container instead of an `init` container.

Currently, Cloudflare WARP [can be installed on](https://pkg.cloudflareclient.com/) Ubuntu, Debian, RHEL, and CentOS. See? Not many Linux distros are supported.

> [Cloudflare WARP](https://one.one.one.one/) is a very popular _free_ VPN. It's fast since it operates on Cloudflare’s global network through WireGuard connection. There's no limit to bandwidth usage. But it can't spoof your location, and it will not work very well with torrenting, since it doesn't support port-forwarding.

However, we can set up WARP in a container, a Distrobox container to be specific, then proxy the host's connection to the container through SOCKS5 port. This method would benefit unsupported system like Arch and openSUSE, for example. Also, immutable OSes like Fedora Silverblue and openSUSE MicroOS would be able to enjoy WARP connection easily.

Without further ado, let's see how to set this up.

👉️ **Table of contents:**

1. Install Distrobox
2. Create a Distrobox Container
3. Install Cloudflare WARP client in the Container
4. Enable WARP Service
5. Register the Client
6. Turn On Malware Filtering (optional)
7. Enable Proxy Mode and Config Proxy Port
8. Connect to WARP
9. Export the Client (`warp-svc`)
10. Reroute Our Host's Connection
11. Verify WARP Connection
12. Automatically Connect to WARP on System Startup + Kill Switch
13. Containers Maintenance

---

# 1. Install Distrobox

[Distrobox](https://github.com/89luca89/distrobox) is a container manager designed to integrate tightly with the host. Compared to Docker and Podman, it's _a lot_ easier to use, as most complicated things are set up OOTB. Nevertheless, it still uses Podman or Docker (not recommended) behind the scene. 

To install Distrobox on openSUSE Tumbleweed, for example:

```
sudo zypper install distrobox
```

> On a very old point-release distro, current Ubuntu LTS for example, you might want to install the latest version of Distrobox using [Homebrew](https://brew.sh/): `brew install distrobox`.

On Tumbleweed, this will pull in Docker automatically.

However, I prefer Podman as Docker Desktop is not supported on my system (openSUSE). 😤 But more importantly, Podman runs rootless by default, while Docker doesn't. In fact, [Docker has rootless mode](https://docs.docker.com/engine/security/rootless/). However, it doesn't work as well, [see Distrobox issue #223](https://github.com/89luca89/distrobox/issues/223). To lump, if you want to use Distrobox as an apps' compatibility layer for your OS, Podman is the way to go. It works great in both rootless and rootful modes.

Therefore, on openSUSE Tumbleweed, for example, I install Podman with `sudo zypper install podman`, then in `~/.config/distrobox/distrobox.conf`, I tell Distrobox to use Podman instead:

```
container_manager="podman"
```

> If you're on Ubuntu, the only way to get the _latest_ Podman is to [build from source](https://podman.io/docs/installation#building-from-source). _Official_ Ubuntu support from upstream [is not going to happen in the foreseeable future](https://github.com/containers/podman/discussions/14360#discussioncomment-3475534). There's a request for Podman as a Snap package [on Snapcraft forum](https://forum.snapcraft.io/t/podman-as-alt-to-docker/36989), though. Please consider voting in that thread, so Canonical might release a snapped Podman one day. 💣️

---

# 2. Create a Distrobox Container

First, we need to decide which OS image we'll use for a container. See a list of supported containers [here](https://github.com/89luca89/distrobox/blob/main/docs/compatibility.md#containers-distros). I recommend [the _official_ Ubuntu 22.04 image](https://hub.docker.com/_/ubuntu) from Docker Hub, as Cloudflare WARP client doesn't support Ubuntu 24.04 yet. So, I create a container with:

```
distrobox create -i docker.io/library/ubuntu:22.04 -n cfw-dbx -H ~/distrobox/cfw-dbx --unshare-netns --volume /run/dbus/system_bus_socket:/run/dbus/system_bus_socket --additional-flags "-p 127.0.0.1:1080:1080"
```

- `distrobox create` is used to create a Distrobox container. See all options of this command [here](https://github.com/89luca89/distrobox/blob/main/docs/usage/distrobox-create.md).
- `-i` is used to specify the image we want to use for the container.
- `-n` is used to specify the container name. In this case, I use `cfw-dbx`.
- `-H` is used to separate the container `$HOME` from the host (I don't want the container's config files, which could be temporary, to be messed up with my system configs). In this case, I specify my host's `~/distrobox/cfw-dbx` to be my container's `$HOME`. All the container's configs will be in this folder.
- `--unshare-netns` is used to separate the container internet from the host. So, the container's internet configurations won't conflict with the host.
- `--volume /run/dbus/system_bus_socket:/run/dbus/system_bus_socket` is used to share DBus system daemon with the host. It's usually needed if the app inside the container complained about `Failed to connect to the bus: Failed to connect to socket /run/dbus/system_bus_socket: No such file or directory`.
- `--additional-flags "-p 127.0.0.1:1080:1080"` is used to add Docker/Podman flags that are not available in Distrobox. In this case, it's necessary to map proxy's SOCKS5 port (1080) on the host to the same port inside the container (we will config WARP's proxy to use this port later).

After the creation process, enter the container by:

```
distrobox enter cfw-dbx
```

> You can simply exit from the container by... using `exit` command inside the container 😂 However, the container will still be running in the background. To stop the container: `distrobox stop <container name>`.

---

# 3. Install Cloudflare WARP client in the Container

You can refer to the _official_ WARP client installation instruction [here](https://pkg.cloudflareclient.com/).

> To complete the instruction without any issue, we need to install these packages:

```
sudo apt install curl lsb-release
```

---

# 4. Enable WARP Service

Since we do not use an `init` container (a container with separated `systemd` and user session), we need to enable WARP service manually. Otherwise, we won't be able to use `warp-cli` to make any connection at all:

```
sudo warp-svc
```

> Do not close this terminal window. You can let it run.

---

# 5. Register the Client

Open a new terminal window, then run:

```
distrobox enter cfw-dbx
```

Or we can simply click on the container's icon, which Distrobox automatically created for us when we created the container:

![Containers](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8edtwqnsvhd0gl4oblr8.png)

Run this command in the container to register the client:
 
```
warp-cli registration new
```

---

# 6. Turn On Malware Filtering (optional)

This is completely optional, but it's good to know that WARP use 1.1.1.1 as its DNS resolver by default. What I always use is 1.1.1.2, which is the same as 1.1.1.1 + malware filter at DNS level. So, why not? 😎

```
warp-cli dns families malware
```

---

# 7. Enable Proxy Mode and Config Proxy Port

Since we want to reroute _all_ the host's internet connections to this container's WARP tunnel, we need to enable the proxy mode and config the proxy port (SOCKS5 - port 1080).

```
warp-cli mode proxy
warp-cli proxy port 1080
```

---

# 8. Connect to WARP

```
warp-cli connect
```

---

# 9. Export the Client (`warp-svc`)

```
distobox-export -b /bin/warp-svc --sudo
```

> Note, `warp-svc` needs to be run as root (inside the container, which is still rootless on the host level), so I specify `--sudo` when exporting.

> If you want, you can create a desktop shortcut to enable the connection. It's exported to `~/.local/bin`. For the icon, you can grab it from [Play Store](https://play.google.com/store/apps/details?id=com.cloudflare.onedotonedotonedotone&amp). 😂

After this, we're free to leave the container. It's a one-time setup. We don't have to enter this container ever again. Exit the container by running `exit` command.

```
exit
```

---

# 10. Reroute Our Host's Connection

Find your host's proxy settings. For example, in GNOME, change your proxy to manual mode, then put in localhost IP (127.0.0.1) and SOCKS5 port (1080) as shown in the screenshots below:

![GNOME's proxy settings #1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gjof1zfaqlsyudmg0dz9.png)

![GNOME's proxy settings #2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ou5ywnlgkecasx4x5rsq.png)

---

# 11. Verify WARP Connection

According to [this Cloudflare blog post](https://blog.cloudflare.com/announcing-warp-for-linux-and-proxy-mode), we can check WARP connection _on the host_ using their trace URL (in our case, from the host's terminal):

```
curl https://www.cloudflare.com/cdn-cgi/trace/ | grep warp
```

If you've set up WARP successfully, this should return:

```
warp=on
```

You can check your current IP and DNS resolvers that should now change to Cloudflare on [dnscheck.tools](https://www.dnscheck.tools/).

---

# 12. Automatically Connect to WARP on System Startup + Kill Switch

Normally, you can connect to WARP in the container just by running the exported bin, `~/.local/bin/warp-svc`, from step #9, since we already set everything up inside the container. And to disconnect from WARP, we can either stop the container or closing the host's proxy.

However, if we want to connect to WARP automatically on boot, it's just as easy. All we need to do is using a _user_'s `systemd` service and a timer.

> Note, for a rootless Podman container to statart successfully on boot, you'll _have to_ use a _user_ service (`--user`), as you can't start a rootless Podman container using a system service. This is the expected behavior, see Podman's [troubleshooting #31](https://github.com/containers/podman/blob/main/troubleshooting.md#31-podman-run-fails-with-erro0000-xdg_runtime_dir-directory-runuser0-is-not-owned-by-the-current-user-or-error-creating-tmpdir-mkdir-runuser1000-permission-denied), Podman issue [#12778](https://github.com/containers/podman/issues/12778) and [#19740](https://github.com/containers/podman/issues/19740).

## 12.1. The Service File

```
[Unit]
Description=Start cfw-dbx container for Cloudflare WARP connection.
RequiresMountsFor=/run/user/1000/containers

[Service]
Type=exec
ExecStart=/home/archerallstars/.local/bin/warp-svc
ExecStop=-bash -c "distrobox stop cfw-dbx"
RemainAfterExit=yes
```

- Replace `archerallstars` with your username.
- Save this service file as `<container-name>.service`. For example, in my case, it's `cfw-dbx.service`. Then, put the file in `~/.config/systemd/user`.

## 12.2. The Timer File

```
[Unit]
Description=Start Cloudflare WARP connection with some delays.

[Timer]
OnStartupSec=10
RandomizedDelaySec=5

[Install]
WantedBy=timers.target
```

- The filename for both the `.service` and its `.timer` _must_ be the same. Therefore, save this file as `~/.config/systemd/user/cfw-dbx.timer`.
- I use some delays, as starting the container too soon without delay could crash the entire system, of which hard reboot would be required! It happens to me many times. And it seems this is _not_ a specific Podman issue, as this also happens with Docker, as [documented on this ArchWiki page](https://wiki.archlinux.org/title/Systemd/User#Runtime_directory_'/run/user/1000'_is_not_owned_by_UID_1000,_as_it_should).

## 12.3. Reload and Enable the Timer

```
systemctl --user daemon-reload && systemctl --user enable cfw-dbx.timer
```

Now, our container along with WARP connection will automatically start _after_ **we logged in to our user session**.

You can see more about `systemd` on [the official man page](https://www.freedesktop.org/software/systemd/man/latest/systemd.html), epecially [this `systemd.exec`](https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html) man page.

> Note, `RequiresMountsFor=/run/user/1000/containers` directive seems to be necessary for a rootless Podman container, see [Podman issue #7330](https://github.com/containers/podman/issues/7330#issuecomment-1053888651). Even though the path should be the same for everyone (it's the default path), you can check yours by running: `podman info | grep runRoot`.

> For a rootfull Podman container, it's `RequiresMountsFor=%t/containers`.

In order to start a user service on boot, not after the login, we need to [enable the user lingering](https://www.freedesktop.org/software/systemd/man/latest/loginctl.html#enable-linger%20USER%E2%80%A6):

```
sudo loginctl enable-linger archerallstars
```

Replace `archerallstars` with your username. After this, your service will be started _on boot_ with the system instead of after the user's login. 

## 12.4. Kill Switch 

The kill switch part... If we don't turn off the system proxy, we can't connect to the internet at all without the container running. 😮 

In other word, we can flip-flop between WARP connection and our normal internet connection using our system's proxy switch.

---

# 13. Containers Maintenance

To make our setup more robust, it has to be able to upgrade itself. To update _all_ Distrobox containers (the containers' OSes, drivers, packages, apps, etc.), we can simply run `distrobox-upgrade --all`.

If you want the system to _automatically_ upgrade all the containers _everyday_ transparently in the background, you can use this `systemd` service file along with its timer running as `--user`:

## `dbx-upgrade.service`
```
[Unit]
Description=Upgrade all rootless Distrobox containers.
RequiresMountsFor=/run/user/1000/containers

[Service]
Type=exec
ExecStart=-bash -c "distrobox-upgrade --all"
Restart=on-failure
RestartSec=60
TimeoutStopSec=5min
RemainAfterExit=yes
```

Save this file as `~/.config/systemd/user/dbx-upgrade.service`.

## `dbx-upgrade.timer`
```
[Unit]
Description=Run distrobox-upgrade --all daily.

[Timer]
OnCalendar=daily
RandomizedDelaySec=5min
Persistent=true

[Install]
WantedBy=timers.target
```

Save this file as `~/.config/systemd/user/dbx-upgrade.timer`.

Then reload user's services and enable it with:

```
systemctl --user daemon-reload && systemctl --user enable dbx-upgrade.timer
```

---

# The Reliability of the Proxy Connection on Linux

![The Man is on the Call](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g8iosvzima7151rkz50z.jpg)

While we can leverage the container approach to deal with the limited platform availability of the client, it comes with another form of limitation due to unreliable support of the proxy connection on Linux. 

To lump, system apps and Snap apps mostly use the proxy just fine. There are only a few exceptions, for example, `curl` respects proxy by default, while `wget` does not.

The issue becomes complicated with apps in containers. While it's true that Distrobox created a container with `--network host` [by default](https://github.com/89luca89/distrobox/blob/867f4f85c762bc74bf398c8654984b801138e154/distrobox-create#L643-L645). Therefore, your system's proxy settings should be applied to apps in all Distrobox containers without issue, right? You can check your connection with `curl --silent https://ipecho.net/plain ; echo`, this should return Cloudflare IP. However, I checked against Brave Browser in the container. It doesn't use the system's proxy connection by default, which is contrary to when it's run outside the container. This _might_ be related to the browser's proxy settings detection method that doesn't work well with containerized environment. Nevertheless, you can use [Proxy SwitchyOmega extension](https://chromewebstore.google.com/detail/padekgcemlokbadohgkifijomclgjgif) to manually config the browser's proxy connection, and the browser inside the container will be able to use the host's proxy just fine. It means that your system's proxy settings can be accessed in the container, but depending on how apps detect the system's proxy settings, the result could be varied.

Lastly, Flatpak. Apps in Flatpak don't respect system proxy settings by default, see Flatpak's xdg-desktop-portal [issue #554](https://github.com/flatpak/xdg-desktop-portal/issues/554). However, this also depends on how the app specifically handles proxy connection in Flatpak. For example, Firefox Flatpak version totally ignores system's proxy settings, while Brave Flatpak respects the system proxy just like the native version.

I hope proxy settings on Linux will improve overtime in the future. As things are today, the leak can happen even with the system's apps/packages. IMO, the system's proxy settings should be forced on every app, just like how WireGuard works.

---

# Alternatives

Why do I recommend you all to set this up instead of all other alternatives out there? 

Well, this method use the _official_ Ubuntu image as a base for our container. It also uses the _official_ WARP client from Cloudflare. So, you are _not_ compromising your system security by running random images or scripts.

Therefore, I won't recommend the alternatives.

---

I hope this helps. If you like this article, please let me know in the comment section below. If you don't, feel free to tell me why. Thanks for reading, bye 💨

---

Cover Photo by [Pawel Czerwinski](https://unsplash.com/@pawel_czerwinski?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash) on [Unsplash](https://unsplash.com/photos/a-blurry-image-of-a-red-object-in-the-dark-W3Jf1SNC8rA?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash)

The Man is on the Call Photo by [YUNAN WANG](https://unsplash.com/@eysmanpics?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash) on [Unsplash](https://unsplash.com/photos/a-man-walking-down-a-street-next-to-a-tall-building-ljwmZLSsV1A?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash)