Solution · 21 min

How to Bypass the Discord Block on Linux

Discord is blocked in Turkey and the desktop app never opens. Instead of a system-wide VPN, I ran Discord alone through Cloudflare WARP. The VPN is up only while Discord runs and only inside Discord; the browser and everything else stay on the normal connection. The ISP sees the hostname sent in the clear during the handshake and cuts the connection — but only over TCP. The browser gets in because it reaches the same site over UDP (HTTP/3, QUIC); Discord's chat gateway runs over TCP only, so the app has no such option. Here is what changing DNS did and did not fix, the network namespace setup, and the leak tests, step by step.

Discord has been blocked in Turkey since October 2024. That is why the desktop app on my Fedora laptop never opened: clicking the icon brought up a small window saying “Progress — Downloading Discord”, and the progress bar never moved. The app never even got far enough to say “I can’t connect” — it couldn’t download itself, so it never started.

What I did: I ran Discord through Cloudflare WARP, Cloudflare’s free VPN service with no data cap. But I did not put the whole machine on the VPN — that was the point.

The scope of the setup:

  • The VPN is up only while Discord is running, and goes down on its own when Discord closes.
  • The VPN applies only inside Discord. The browser, system updates, Docker, banking — all of it goes over the normal connection.
  • Nothing changes on the rest of the machine: system DNS, routing table and firewall stay as they were.
  • When Discord closes, no namespace, tunnel or rule is left behind; all of it is removed.

I did this with Linux network namespaces. The steps are below.

One more detail: discord.com opened in my browser while the app did not. Three months earlier I had changed the DNS on my router, and that change did work — but only in the browser, and only for certain sites. It did nothing for the Discord app. It isn’t the reason for the difference here either; I ruled that out by testing. The difference isn’t the DNS, it’s the protocol. Getting there took me a while: I had the diagnosis wrong at first and corrected it by testing. That part is in here too.

My system / test environment#

  • OS: Fedora 44, KDE Plasma (Wayland)
  • Discord: package 1.0.163 (terra repo), app 1.0.164
  • Browser: Brave
  • DNS: 1.1.1.1 + 1.0.0.1 on the router (both Cloudflare)
  • ISP: Türk Telekom, Turkey
  • Wireless interface: wlo1

A few of the commands below use the wireless interface name; yours may differ:

ip -br link
lo          UNKNOWN   00:00:00:00:00:00 <LOOPBACK,UP,LOWER_UP>
wlo1        UP        xx:xx:xx:xx:xx:xx <BROADCAST,MULTICAST,UP,LOWER_UP>
docker0     UP        xx:xx:xx:xx:xx:xx <BROADCAST,MULTICAST,UP,LOWER_UP>

(I masked the MAC addresses and trimmed Docker’s bridges.)

A wireless interface name starts with wl: wlo1, wlan0, wlp3s0. A wired one starts with en or eth. lo is the loopback; docker0 and anything starting with br- are Docker’s virtual bridges, not what you want.

Where is the block actually placed?#

This matters before the fix, because if you don’t know which kind of block you’re facing you will use the wrong tool.

Three things happen when you open a site, and the block can sit at any one of them:

WhereWhat happensHow the block works
1. Address lookup (DNS)“Which server is discord.com on?”The ISP’s DNS returns a wrong address, or none at all
2. The hostname sent while connecting (SNI)The browser or app tells the server which site it wants — and this part is sent in the clearThe ISP sees that name and kills the connection
3. Blackholing the server’s address (IP)The route to the server is cut entirelyNo packet gets through

Each one is defeated by something different: the first by using another DNS, the third only by a VPN. Discord falls in the second group. The lookup works fine and the server’s IP is reachable; the ISP cuts the connection because it sees the name “discord.com” sent in the clear while it is being set up.

What I had already done: changing DNS#

Three months before this setup, in July, I changed the DNS settings on my router. Let me cover that first, because it explains why it wasn’t enough for Discord.

The problem back then was that some sites opened sometimes and not others. What they had in common was sitting behind Cloudflare. The cause surprised me:

  • Türk Telekom has blackholed Cloudflare’s 188.114.96.0/22 address range entirely (method 3 in the table above).
  • Google DNS (8.8.8.8) kept handing me addresses from that blackholed range for those sites.
  • Cloudflare DNS (1.1.1.1) kept handing me addresses from a working range (104.21.x, 172.67.x).
  • Both were configured on the router. Sometimes the machine used Google’s answer and the site failed; sometimes it used Cloudflare’s and the site loaded.

So those sites weren’t blocked at all; my DNS was handing me a blocked address. The same site loaded fine from a different address.

Removing 8.8.8.8 and leaving only 1.1.1.1 fixed it completely (15 out of 15 attempts). I later added 1.0.0.1 as a backup — also Cloudflare, and it returns the same working range. If you leave only one DNS server, losing it takes your whole connection down with it.

# Which DNS is in use?
resolvectl status wlo1 | grep "DNS Servers"
# Expected: DNS Servers: 1.1.1.1 1.0.0.1

# What address does a site resolve to?
getent ahosts discord.com
162.159.128.233 STREAM discord.com
162.159.128.233 DGRAM
162.159.128.233 RAW
162.159.135.232 STREAM
...

The first column is the address. Here it is 162.159.x, a working range. If you see 188.114.96.x or 188.114.97.x, you have been handed an address from the range the ISP blackholed, and that site will open sometimes and fail other times.

None of this helped with Discord. In fact Discord was never affected by that problem at all — I checked:

dig +short A discord.com @1.1.1.1   # 162.159.136.232, 162.159.137.232 ...
dig +short A discord.com @8.8.8.8   # 162.159.135.232, 162.159.138.232 ...

Both resolvers return 162.159.x, both from a working range. The blackholed 188.114.96.0/22 range never comes up for Discord. The sites Google DNS broke were different ones; Discord wasn’t among them.

It was never going to help anyway: changing DNS defeats blocks placed at point 1, and Discord is blocked at point 2. I was always learning the correct address; the ISP cut the connection because it saw the hostname I sent while connecting to that address.

The real benefit of the DNS change was elsewhere. It made some sites load in the browser, and it brought a side effect: with the system DNS set to Cloudflare, Brave starts using it encrypted (DNS-over-HTTPS), so the ISP can no longer see what I’m looking up either.

Where exactly does the app get stuck?#

To find out what Discord was trying to download, I looked inside the package:

rpm -ql discord
/usr/bin/discord
/usr/share/applications/discord.desktop
/usr/share/discord
/usr/share/discord/updater_bootstrap
/usr/share/pixmaps/discord.png

Discord itself is not in the package. There is only a downloader called updater_bootstrap.

/usr/bin/discord turned out to be a small shell script. What it does, in short:

discord_host=$HOME/.config/discord/Discord      # the real app should be here

if [ ! -x "$discord_host" ]; then               # if it isn't:
    # open the "Downloading Discord" window and
    # fetch it from https://updates.discord.com/
    app_dir=`/usr/share/discord/updater_bootstrap --zenity "$HOME/.config/discord" stable https://updates.discord.com/`
    exec "$HOME/.config/discord/$app_dir/Discord"
fi
exec "$discord_host"

My ~/.config/discord/ held an old installer.db and no app. That pinned down where it was stuck: it couldn’t reach updates.discord.com, so the download never began.

Which Discord addresses are blocked?#

for h in updates.discord.com dl.discordapp.net discord.com gateway.discord.gg; do
  printf "%-22s " $h
  curl -s -o /dev/null -w "%{http_code}\n" --max-time 10 https://$h/
done
updates.discord.com    000
dl.discordapp.net      301
discord.com            000
gateway.discord.gg     000

000 means no response at all. curl exited with code 35 (SSL connect error) and the connection died in 0.05 seconds. That speed is the signature of a block placed at point 2: the packet never reaches the far side, a device in between cuts it before the handshake finishes.

Two things stood out:

  • dl.discordapp.net is open (it returned 301). It’s a file server and its name doesn’t contain “discord.com”. That shows exactly which names the filter looks at and which it ignores.
  • gateway.discord.gg is blocked. That’s Discord’s chat gateway. So rolling back to an older version wouldn’t have helped: the app would open and still fail to reach my account. If you see “install an older version” advice anywhere, this is why it doesn’t work.

The real question: how does the browser get in?#

Everything so far was what I expected. The one thing left unexplained was this: at the same time, discord.com opened in my browser.

The obvious explanation is the DNS change on the router, but it can’t be. Two reasons:

  • discord.com resolves to the same working range on both 1.1.1.1 and 8.8.8.8 (shown above), so Discord was never affected by that problem.
  • DNS is the same for the whole machine. The browser and curl learn the same address, yet one gets in and the other doesn’t.

The second one is the real proof: same machine, same DNS, same address — different result. So the difference isn’t the address, it’s how you connect to it. The browser was doing something the ISP doesn’t inspect. What?

A wrong diagnosis (an honest note)#

My reasoning was: “The browser must be using Encrypted Client Hello (ECH). ECH encrypts the hostname sent during the handshake too. The ISP can’t see the name, so it can’t cut the connection. The Discord app has no ECH, so it announces the name in the clear and gets cut.”

It sounded reasonable, and I accepted it for a while. The fact that ECH exists precisely to defeat this kind of block made it more convincing still.

Then I tested it. ECH works through a key the site publishes in DNS; if the site doesn’t publish that key, ECH can never come into play:

dig +short HTTPS discord.com           @1.1.1.1
dig +short HTTPS gateway.discord.gg    @1.1.1.1
dig +short HTTPS updates.discord.com   @1.1.1.1
dig +short HTTPS crypto.cloudflare.com @1.1.1.1   # for comparison
discord.com            1 . alpn="h3,h2" ipv4hint=162.159.128.233,...
gateway.discord.gg     1 . alpn="h2"    ipv4hint=162.159.130.234,...
updates.discord.com    1 . alpn="h3,h2" ipv4hint=162.159.128.233,...
crypto.cloudflare.com  1 . alpn="h2"    ipv4hint=162.159.135.79,... ech=AEX+DQBBJgAgACB7PKDZ...

(I trimmed the address lists.)

None of Discord’s addresses carry an ech= field. The comparison address, crypto.cloudflare.com, has one at the end of its line.

There is a second thing in this output that becomes useful later: the gateway.discord.gg line says only alpn="h2", with no h3. The other two addresses do list h3. So the chat gateway never advertises HTTP/3 at all.

So my diagnosis was wrong. Discord doesn’t publish ECH, which means the browser isn’t reaching Discord over ECH either. Forcing it with a flag like --enable-features=EncryptedClientHello wouldn’t work for the same reason.

But testing that wrong idea is what produced the real answer: the h3 in alpn="h3,h2" means HTTP/3.

The real cause: the block only cuts TCP#

I tried connecting to the same address over two different protocols:

curl -s -o /dev/null -w "%{http_code}\n" --http2      https://discord.com/   # over TCP
curl -s -o /dev/null -w "%{http_code}\n" --http3-only https://discord.com/   # over UDP
000    ← TCP: cut
200    ← QUIC (UDP): works
AddressTCP (HTTP/2)UDP (HTTP/3, QUIC)
discord.com❌ cut✅ 200
updates.discord.com❌ cut✅ 404 (reached)
gateway.discord.gg❌ cut❌ no QUIC support

That was the answer: the ISP sees the hostname and kills the connection, but only on TCP. The same traffic over UDP, as HTTP/3 (QUIC), is not blocked.

Brave uses QUIC by default, which is why the site loads in the browser. curl defaults to TCP, which is why it didn’t. Getting two different results on the same machine came down to which protocol I was using, not to the block.

So why can’t the app use QUIC? The last row answers that: gateway.discord.gg doesn’t support QUIC. The DNS output above said the same thing: that address advertises only alpn="h2". Two separate sources, same conclusion. Discord’s chat connection is a WebSocket and runs over TCP only. Whatever the app does, it has to open that connection — and that is exactly what gets cut.

The Discord page loads in the browser because QUIC exists; the desktop app doesn’t open because its chat gateway knows no path other than TCP. The only way to run the app is to put its traffic in a tunnel the ISP cannot inspect at all.

The fix: a VPN, but only inside Discord#

I didn’t want the whole machine on a VPN:

  • Banking and official business should go over the normal connection.
  • I run Docker networks and a remote-access setup; a system-wide VPN can collide with those.
  • Putting an entire machine in a tunnel for one app’s problem is overkill.

Linux’s direct answer for this is the network namespace. You give a program its own isolated network stack; inside it the program sees its own network, not the system’s.

The machine
│
├─ Brave · updates · Docker · everything else
│    └──► Normal internet        (your own IP, warp=off)
│
└─ Discord's network namespace
     └──► WARP tunnel ──► Cloudflare ──► Discord servers
                                         (warp=on)

The tools I used, all free and open source:

ToolWhat it does
Cloudflare WARPCloudflare’s free VPN service, no data cap
wgcfRegisters a free WARP account and generates a WireGuard config
wireguard-toolsThe tunnel itself
voponoRuns a program in its own network namespace over a given VPN

vopono does the real work: it creates the namespace, brings the tunnel up inside it, starts the program there, and tears everything down when the program exits.

Setting it up#

I downloaded the tools into a separate folder and checked the download against the checksum the publisher provides:

mkdir -p ~/Applications/discord-vpn/download && cd ~/Applications/discord-vpn/download

curl -LO https://github.com/ViRb3/wgcf/releases/download/v2.3.1/wgcf_2.3.1_linux_amd64
curl -LO https://github.com/ViRb3/wgcf/releases/download/v2.3.1/checksums.txt
curl -LO https://github.com/jamesmcm/vopono/releases/download/1.0.2/vopono_1.0.2_amd64_fedora.rpm

sha256sum wgcf_2.3.1_linux_amd64     # compared against checksums.txt: matched
cd ..
install -m 755 download/wgcf_2.3.1_linux_amd64 ./wgcf
sudo dnf5 install wireguard-tools ./download/vopono_1.0.2_amd64_fedora.rpm

Register the free WARP account and generate the WireGuard config:

./wgcf register --accept-tos
./wgcf generate                       # → wgcf-profile.conf

Before building the rest, I confirmed the tunnel worked:

vopono exec --custom ./wgcf-profile.conf --protocol wireguard \
    "curl -s https://www.cloudflare.com/cdn-cgi/trace"
warp=on
loc=TR

The warp=on line says traffic inside that namespace leaves through WARP. loc=TR says the location still reads as Turkey: WARP uses the nearest Cloudflare edge.

Then a small launcher, so I wouldn’t type that command every time:

#!/usr/bin/env bash
# Runs Discord in its own network namespace, over Cloudflare WARP.
cd "$(dirname "$0")"
exec vopono exec --custom ./wgcf-profile.conf --protocol wireguard discord "$@" \
     >>"$HOME/.local/state/discord-vpn.log" 2>&1

Finally I pointed the menu entry at that launcher. A .desktop file in the user directory shadows the system one, so no second entry shows up in the menu:

sed -e "s|^Exec=.*|Exec=$HOME/Applications/discord-vpn/discord-vpn.sh|" \
    -e "s|^Name=Discord$|Name=Discord (VPN)|" \
    /usr/share/applications/discord.desktop > ~/.local/share/applications/discord.desktop
update-desktop-database ~/.local/share/applications

This command prints nothing; it writes a file. Two lines tell me it was written correctly:

grep -E "^(Name|Exec)=" ~/.local/share/applications/discord.desktop
Name=Discord (VPN)
Exec=/home/user/Applications/discord-vpn/discord-vpn.sh

Exec now points at my launcher instead of /usr/bin/discord. Now clicking the menu entry opens Discord, the VPN comes up automatically, and it goes down when Discord closes. Nothing else to start or stop. After the setup, Discord downloaded itself through the tunnel and opened.

Verifying it#

This is the part that matters most to me: a setup looking like it works and actually working correctly are two different things.

Is Discord really in its own namespace?

sudo ip netns identify $(pgrep -xo Discord)
# vo_c_FDYSro1

Is the rest of the machine affected?

curl -s https://www.cloudflare.com/cdn-cgi/trace | grep warp=
# warp=off          ← the host is not on the VPN

curl -s -o /dev/null -w "%{http_code}\n" https://discord.com/
# 000               ← still blocked, so the VPN isn't leaking out

The second command is the real proof. If discord.com is still unreachable from the host, the VPN really is confined to Discord’s namespace. Had it loaded, I’d know the VPN had leaked into the system.

If the tunnel drops, does traffic fall back to the normal connection?

vopono installs a kill switch inside the namespace. Reading that in the docs isn’t enough, so I tested it: I brought the tunnel down by hand and tried to connect from inside the namespace.

ns=vo_c_FDYSro1
sudo ip netns exec $ns ip link set $ns down                              # drop the tunnel

sudo ip netns exec $ns curl -4 https://1.1.1.1/                          # exit 7 (refused)
sudo ip netns exec $ns curl -6 'https://[2606:4700:4700::1111]/'         # exit 7 (refused)
sudo ip netns exec $ns dig @8.8.8.8 discord.com                          # connection refused
sudo ip netns exec $ns curl --interface ${ns}_s https://1.1.1.1/         # exit 7 (refused)

All four refused: IPv4, IPv6, TCP and UDP. When the tunnel drops, Discord’s traffic does not fall back to the normal connection — it is blocked outright. Bringing the tunnel back up restored warp=on.

How much does the VPN slow things down?

I ran the same test 10 times with the VPN off and 10 times from inside Discord’s VPN namespace. Each run measured the same thing: how many milliseconds it takes to open a TCP connection to 1.1.1.1. The table summarises those runs:

AverageMinMaxJitter
Without VPN26.7 ms22.0 ms36.2 ms4.6 ms
With WARP30.0 ms23.7 ms75.6 ms15.2 ms

An average difference of +3.3 ms. WARP uses the Cloudflare edge in Istanbul (colo=IST), which is why the latency stays low. I joined a voice channel, talked, and shared my screen — both worked without trouble.

Why I didn’t pick the other approaches#

There is more than one way past this block, and each one falls short somewhere else. Which one suits you depends on what you prioritise:

This setup (vopono + WARP)byedpi / SpoofDPIzapret (nfqws)
How it worksAll of Discord’s traffic inside an encrypted tunnelLocal proxy; splits the TLS packet so the ISP cannot read the hostnameKernel level; splits packets going to listed addresses
Voice (UDP)✅ Goes through the tunnel❌ Doesn’t go through the proxy✅ Unaffected
Needs a VPNYesNoNo
Needs rootsudo on every launchNoPersistent service + firewall rule
ScopeDiscord onlyDiscord onlyWhole system, but only listed addresses

I didn’t try byedpi or zapret; a VPN was simply the first thing I reached for. The packet-splitting approaches are lighter and may suit you better, but the two are not interchangeable:

  • byedpi is a proxy, so it doesn’t carry voice traffic. On its own it isn’t enough if you use voice channels.
  • zapret leaves voice alone, so that side is fine. In exchange it installs a permanent system-wide service and a firewall rule — deeper into the system than my setup, which only asks for sudo at launch.

So there is no single “better” answer. If you don’t use voice channels, byedpi is the lightest; if a permanent system-wide rule doesn’t bother you, zapret is the least hassle.

I also didn’t use Cloudflare’s official WARP client. The Linux version has no “this app only” option. You choose what goes through the tunnel by address, not by application — meaning you would list every Discord server address by hand. The voice servers aren’t fixed either, so keeping that list current is a job of its own. In its default mode it puts the whole machine on the VPN, which was exactly what I was avoiding.

Things worth knowing#

These didn’t come up during the setup, but afterwards in day-to-day use:

  • Links inside Discord. At first there was a hole: if the browser was closed and I clicked a link in Discord, the browser opened inside Discord’s namespace and went through the VPN too. For a permanent fix I wrote a small xdg-open wrapper that launches the browser via systemd-run --user, from the user’s systemd manager — so links always open in the browser on the normal network.

  • The connection that shows up in the network applet. While Discord runs, KDE’s network icon shows a “wired connection” named vo_c_.... That is the host-side end of the virtual cable into Discord’s namespace. No other program uses it, and it disappears when Discord closes.

  • Your location isn’t hidden. WARP hides your real IP from Discord, but the location still reads as Turkey, because WARP uses the nearest Cloudflare edge. The goal is getting past the block, not anonymity.

  • Root privileges. Creating a network namespace requires sudo. If you don’t want a password prompt on every launch, the right answer is vopono daemon, its privileged background service. Making sudo passwordless outright looks easier but is a serious trade-off for the whole system; I wouldn’t recommend that route.

  • Updates. Discord updates from dnf don’t break the menu entry, and Discord downloads its own internal updates through the tunnel.

Frequently asked questions#

Is my whole machine on the VPN?

No. Open cloudflare.com/cdn-cgi/trace in your browser and you’ll see warp=off. The VPN is up only while Discord runs, and only inside its namespace.

Do voice chat and screen sharing work?

Yes, I tested both and had no issues. The namespace approach carries Discord’s UDP traffic through the tunnel as well.

Isn’t changing DNS enough on its own?

No. Changing DNS only defeats blocks placed at point 1. Discord is blocked at point 2: you learn the correct address, then get cut while connecting to it.

Can I just use Discord in the browser?

The page loads, because the browser uses QUIC. But the chat gateway doesn’t support QUIC, so you need to check separately whether messages actually arrive; the page may load and then sit on “connecting…”.

Does WARP cost anything?

No. I’m using a free WARP account with no data cap.

Couldn’t I just roll back to an older Discord version?

No. The chat gateway gateway.discord.gg is blocked too, so the app would open and still fail to reach your account.

Can this be used for other programs?

Yes. The vopono exec ... <program> pattern works for any program. The logic is the same: give the program its own network namespace and run the VPN only there.

Is doing this legal?

The legal status of access blocks and of circumventing them varies by country and can change over time. This post is a technical write-up; the decision and the responsibility are yours.

Conclusion#

I knew Discord was blocked from the start. What I didn’t know was what the block actually looks at, and what it cuts.

The ISP doesn’t shut Discord off outright. It sees the hostname sent in the clear during the handshake and cuts only TCP connections; it does not block the same traffic when it travels over UDP. That was the sole reason my browser got in and my app didn’t: one can use QUIC, the other’s chat gateway cannot.

I learned one more thing from this: an explanation sounding reasonable doesn’t make it true. “The browser gets in because it uses ECH” was plausible, and ECH was invented for precisely this kind of block. But testing showed Discord never publishes ECH at all. Testing the wrong idea is what produced the right answer; without the alpn="h3,h2" line I saw while looking for an ECH record, I probably wouldn’t have noticed.

I ran into the same thing while fixing my virtual machine’s internet problem: there too the settings looked correct, and that is exactly what misled me. Both times the right answer came from the commands I ran, not from guessing.

Quick reference commands#

# 1) Is the block on TCP? (000 then 200 means yes)
curl -s -o /dev/null -w "%{http_code}\n" --http2      https://discord.com/
curl -s -o /dev/null -w "%{http_code}\n" --http3-only https://discord.com/

# 2) Does the site publish ECH? (no "ech=" means ECH won't solve it)
dig +short HTTPS discord.com @1.1.1.1

# 3) Is the DNS right? (188.114.96.x means the wrong DNS is in use)
resolvectl status wlo1 | grep "DNS Servers"

# 4) Is Discord really in its own namespace?
sudo ip netns identify $(pgrep -xo Discord)

# 5) Is the VPN leaking out? (host should show warp=off and 000 for discord.com)
curl -s https://www.cloudflare.com/cdn-cgi/trace | grep warp=
curl -s -o /dev/null -w "%{http_code}\n" https://discord.com/

If you’re up against the same block, tell me in the comments. Mentioning your distribution and your ISP helps others too — the blocking method differs from one provider to the next.

Comments

Comments use your GitHub account. Clicking “Show” loads Giscus (giscus.app).