v3.41.0

passteque/gluetunv3.41.0Dec 25, 2025by qdm12

AI Summary

Major feature release introducing DNS over HTTPS/TLS support, a revamped healthcheck system, and significant configuration updates.

Key Highlights

  • DNS over HTTPS (doh) and DNS over TLS (dot) support
  • Complete rework of the healthcheck system with startup, periodic full, and periodic small checks
  • Local network names resolution for Kubernetes users
  • Control server role-based authentication system

Breaking Changes

  • Configuration option renames (e.g., DOT to DNS_SERVER, DOT_PROVIDERS to DNS_UPSTREAM_RESOLVERS)
  • Alpine upgraded from 3.20 to 3.22
  • Go upgraded from 1.23 to 1.25

New Features

  • DNS over HTTPS and DNS over TLS support
  • Healthcheck system rework
  • Control server role-based authentication
  • VPN port forwarding template variables support
  • Public IP data fetcher queries sources in parallel

Full Release Notes

[Video of me reading out this release](https://youtu.be/SSkGpys40ck)

Thank you all for your patience for this release which took its sweet time šŸ™ā²ļø

I have been rather absent in a good part of 2025 due to work and life getting in the way, and I would like to thank many of you for helping out around in issues and discussions, and for the few code contributors whilst I was away.

On this release, many of the features you see are the result of behind-the-scene work of the last few years (notably on [dns](https://github.com/qdm12/dns/tree/v2.0.0-beta)) and I'm super glad they are finally in Gluetun! A lot more to come in v3.42.0, there is already a pile of pull requests waiting šŸš€

Final note, introducing the [**RANTING SECTION**](#the-ranting-section) at the bottom of this changelog. This section might also be in the future releases (unfortunately)!

Happy holidays! šŸŽ„ šŸŽ… ā„ļø ⛄ 

## Features

- DNS
  - (**K8s users** read this) **Local network names resolution** using private DNS resolvers found at container start (#2970)
  - **DNS over HTTPS** support (see `DNS_UPSTREAM_RESOLVER_TYPE` below)
  - `DNS_UPSTREAM_RESOLVER_TYPE` option which can be `dot` (DNS over TLS), `doh` (DNS over HTTPS) or `plain` (plaintext over UDP)
  - DNS over TLS re-uses TCP connections which should put less stress on TCP-connections-rate-limiting by the VPN server
  - DNS requests blocked are logged with a reason
  - DNS rebinding protection is always enabled, but hostnames can be excluded with `DNS_REBINDING_PROTECTION_EXEMPT_HOSTNAMES`
  - `i/o timeout` errors are now logged at the debug level instead of warn level
- [**healthcheck system reworked**](https://github.com/qdm12/gluetun-wiki/blob/main/faq/healthcheck.md): more robust and less impact on other applications (#2923)
  - Three checks are performed:
    - **startup full check**: when the VPN connection is first established, perform a TCP+TLS dial to `HEALTH_TARGET_ADDRESSES` with a timeout of 6 seconds
    - **periodic full check**: every 5 minutes, perform a TCP+TLS dial to `HEALTH_TARGET_ADDRESSES`, with up to 3 tries of 10s, 15s, and 30s timeouts
    - **periodic small check**: every minute, perform ICMP pings to `HEALTH_ICMP_TARGET_IPS`, with a fallback to plain DNS (UDP) lookups of `github.com` to cloudflare+google, with up to 10 tries of 5s, 5s, 5s, 10s, 10s, 10s, 15s, 15s, 15s, and 30s timeouts
  - If any of these checks fail, the VPN connection is restarted
  - Reduced impact on TCP to allow for higher bandwidth in TCP torrenting
  - New option `HEALTH_TARGET_ADDRESSES=cloudflare.com:443,github.com:443` to have a fallback address
  - New option `HEALTH_ICMP_TARGET_IPS=1.1.1.1,8.8.8.8` to have `8.8.8.8` as a fallback address
  - New option `HEALTH_SMALL_CHECK_TYPE` which can be `dns` or `icmp`. By default it uses icmp and falls back to dns if icmp isn't permitted.
  - New option `HEALTH_RESTART_VPN`: you should really leave it to `on`, unless you have trust issues with the healthcheck.
- built-in servers data updates:
  - Cyberghost
  - ExpressVPN
  - Mullvad
  - Privado
  - Private Internet Access
  - SlickVPN (mere 29 hardcoded servers 🤷)
  - ProtonVPN
  - Surfshark
  - Torguard
- control server:
  - `HTTP_CONTROL_SERVER_AUTH_DEFAULT_ROLE` option (JSON encoded). For example: `{"auth":"basic","username":"me","password":"pass"}` or `{"auth":"apiKey","apikey":"xyz"}` or `{"auth":"none"}`.
  - log number of roles read from auth file
- VPN server side port forwarding:
  - support `{{PORT}}` template variable on top of `{{PORTS}}`
  - support `{{VPN_INTERFACE}}` template variable which is by default `tun0`
- Public IP data fetcher queries all data sources in parallel and picks the most popular result
- bump Alpine from 3.20 to 3.22
- wireguard: on error parsing `WIREGUARD_ENDPOINT_IP`, mention it must be an IP address for the time being
- new ascii logo logged out at program exit... did any of you spot it? šŸ‘€ 

## Fixes

- Wireguard:
  - specify IP family for new route (#2629)
  - `WIREGUARD_ENDPOINT_IP` regression (v3.39.0) fixed to override the IP address of a picked connection
- Providers specific:
  - Cyberghost: log warnings from updater resolver but not for "no such host" which happen quite a lot
  - ExpressVPN: update hardcoded servers data (#2888)
  - ProtonVPN: authenticated servers data updating
    - If updating servers data periodically, use `UPDATER_PROTONVPN_EMAIL` and `UPDATER_PROTONVPN_PASSWORD`
    - If using the CLI, use `-proton-email` and `-proton-password` flags
  - PureVPN:
    - update OpenVPN configuration settings (from #2991 credits to @mlapaj)
    - updater parses country and city from hostname and merges with ip address information (#2991)
  - VPN Unlimited: update certificates value (#2835) and remove no longer valid hardcoded hosts
  - VPN Secure updater fixed by allowing their website servers list to have "N / A" region/city
  - WeVPN: removed since it decomissioned
- Servers storage: do not crash the container but log a warning if flushing merged servers to file fails
- VPN server side port forwarding:
  - clear port file instead of removing it (see [why](https://github.com/qdm12/gluetun/issues/2942#issuecomment-3468510402))
  - remove double log when clearing port forward file
- Control server:
  - log out full URL path not just bottom request URI
  - change route with retrocompatibility from `/v1/openvpn/portforwarded` to `/v1/portforward`: this route has nothing to do with openvpn specifically, removed the `ed` in `portforwarded` to accomodate future routes such as changing the state of port forwarding
- `PUBLICIP_ENABLED` is now respected
- publicip/api/cloudflare: add now required `Referer` header (#3058)
- cli `openvpnconfig` command no longer panics due to missing `SetDefaults` call
- DNS:
  - retry on next period if a blocklists update failed previously
  - fix `DNS_KEEP_NAMESERVER` behavior (by the way, you should no longer need to use this option!)
    - no longer hangs the code when establishing the VPN connection
    - no longer makes Gluetun panic when exiting
- Healthcheck:
  - fix grammar issue in log (#2773)

## Documentation

- Readme
  - remove no longer valid LoC badge
  - update Alpine version and image size
  - warning on "official" websites which are scams
  - add star history graph because it's fun
- Dockerfile: specify default PUID and PGID to avoid confusion, since both are already defaulted to 1000 in the Go code
- add pull request template (#2918)
- update provider issue template

## Maintenance

- Code
  - Change DNS option names with retro-compatibility:
    - `DOT` to `DNS_SERVER`
    - `DOT_PROVIDERS` to `DNS_UPSTREAM_RESOLVERS`
    - `DOT_PRIVATE_ADDRESS` to `DNS_PRIVATE_ADDRESSES`
    - `DOT_CACHING` to `DNS_CACHING`
    - `DOT_IPV6` to `DNS_UPSTREAM_IPV6`
    - `DOT_PRIVATE_ADDRESS` split into `DNS_BLOCK_IPS` and `DNS_BLOCK_IP_PREFIXES`
    - `UNBLOCK` with `DNS_UNBLOCK_HOSTNAMES`
  - clear `DNS_BLOCK_IP_PREFIXES` values since DNS rebinding protection is built-in the filter middleware
  - internal/vpn: rename `openvpn*` to `vpn*` variables
  - internal/configuration/settings:
    - merge DoT settings with DNS settings
    - remove unneeded Health struct fields
  - `internal/storage`:
    - do not read/write to user file when updating in maintainer mode
    - ignore persisted servers data with a timestamp in the future
  - `internal/publicip/api/ip2location: rename countries to match standard country names from the mapping `constants.CountryCodes()`
- dependencies
  - bump Go from 1.23 to 1.25
  - bump github.com/breml/rootcerts from 0.2.19 to 0.3.3 (#2683, #2964)
  - bump github.com/klauspost/compress from 1.17.11 to 1.18.1 (#2957)
  - bump github.com/pelletier/go-toml/v2 from 2.2.3 to 2.2.4 (#2958)
  - bump github.com/qdm12/dns from v2.0.0-rc8 to v2.0.0-rc10
  - bump github.com/stretchr/testify from 1.10.0 to 1.11.1 (#2959)
  - bump github.com/ulikunitz/xz from 0.5.11 to 0.5.15 (#2955)
  - bump github.com/vishvananda/netlink from 1.2.1 to 1.3.1 (#2932)
  - bump golang.org/x/crypto from 0.29.0 to 0.45.0 (#2619, #2999)
  - bump golang.org/x/net from 0.31.0 to 0.47.0 (#2648, #2937, #2976)
  - bump golang.org/x/sys from 0.29.0 to 0.38.0 (#2939, #2973)
  - bump golang.org/x/text from 0.21.0 to 0.31.0 (#2938, #2975)
- upgrade linter to v2.4.0
  - migrate configuration file
  - fix existing code issues
  - add exclusion rules
  - update linter names
- CI
  - run container and wait for it to connect for both Mullvad and ProtonVPN (#2956)
  - bump github actions and use go.mod Go version (#2880)
  - pull container images at build time from ghcr.io when possible
    - reduce silly image pull rate limiting from docker hub registry
    - still rely on docker hub registry to pull golang and alpine images since these are not on ghcr.io
  - ignore .github/pull_request_template.md with markdown linter
  - consider 429 as valid status code for markdown links
  - bump actions/setup-go from 5 to 6 (#2929)
  - bump actions/checkout from 5 to 6 (#3001)
  - bump DavidAnson/markdownlint-cli2-action from 18 to 21 (#2632, #2984)
  - bump github/codeql-action from 3 to 4 (#2935)
  - bump peter-evans/create-or-update-comment from 4 to 5 (#2931)
- dev setup
  - upgrade dev container to [v0.21](https://github.com/qdm12/godevcontainer/releases/tag/v0.21.0)
  - convert .vscode/launch.json to tasks.json
  - add vscode `git remote add` task

## The ranting section

<details>

<summary>
šŸ„€ this is a new section in which I'll share my rant among various Gluetun-related things 🌻
šŸ’ expect a lot of uppercasing, heavy punctuation and no structure whatsoever. Enjoy the read ā—
</summary>

**ALPINE!!!** STOP BREAKING IPTABLES ON EVERY TWO RELEASES! When I enter `iptables -nL`, `-n` means **NUMERIC**! Then why the hell did `0` become `all` on Alpine 3.22??!!!?!
Gluetun was configured like clockwork to parse these numeric values, and all hell broke lose on some systems where it would return TEXTUAL values!

šŸ’ 2e2e5f9df5a134238e084177c676b9ac4bd708e8 and 6712adfe6b72208815ac099e987702e6efabfa58 for more information

---

**PUREVPN** did change everything for OpenVPN: certificates, keys, CAs. Like, can't you keep the previous ones working instead of breaking everyone? No-one was really warned on this as far as I know, so obviously Gluetun started failing more and more with PureVPN. Thanks to @mlapaj for patching this and notifying me.

---

SlickVPN: Ok fine you're going bankrupt or something, but I spent hours programming code to scrape your locations webpage for you to just add some ugly-ass text directly to list your mere 11 servers left? Couldn't you update the table on your website, which, by the way, is still there below, but empty!!? What the heck!? I ended up throwing all my code and just hardcoding their silly ~11~ 29 servers in Gluetun, because I'm not spending more hours fixing this scrapper, this is ridiculous.

https://www.slickvpn.com/locations/

---

Ok I'm not going to write the url here but it's `h**ps://gluetun.com`. It's an AI generated bullshlt website from some Pakistani idiot in the UK, trying to advertise for themselves to sell "website development" (=AI prompts). I did reach out to them telling them to please shut it down, no answer obviously. I suppose I should trademark gluetun... At least, since it's fully AI-generated, it's almost decent information and there is a bit of honesty in there, like "Not affiliated with Gluetun" at the bottom, although it also says "We at Gluetun" šŸ˜„ 

---

And keeping the best for last: **PROTON!**... Ah Proton... Proton Proton Proton...

First of all, let's start with Proton blocking [their VPN servers data](https://api.protonvpn.ch/vpn/logicals) behind a login wall.
There is no reason for this. None. Zip. Zero. Nada.
You can literally connect to a VPN server with a free account.
And anyone with a paid account, including me, could just get that list and share it.
Absolute non-sense of a choice.

But, fine, let's see what's next...

I exchange with other Gluetun users trying to debug how to access this list, how to login programmatically to get that stupid list.
We all throw our keyboards at our monitor out of frustration because Proton's login system is an overly complex thing.
I decide to contact Proton support.

Ah, Proton "support"...
It's like subconsciously they want their users to run away.

I opened a support ticket explaining the situation, very politely of course, and simply asking for a tiny bit of guidance on helping out with the `curl` commands necessary to login and obtain a valid token.

Their answer? Polite "go away leave us alone" message:

> Public access to the <https://api.protonvpn.ch/vpn/logicals> endpoint is no longer available due to internal changes and security reasons.

WHAT SECURITY REASONS!??? You are making a fool of yourselves Proton!

> Additionally, the setup in question is not officially supported on our end; therefore, I will be unable to provide any steps on how to achieve it, nor guarantee that it will work.

DO YOU THINK I AM STUPID PROTON!??? AND THANKS FOR BEING SO HELPFUL YOU BUNCH OF 10-NEURONS SUPPORT!

> We strongly recommend using the native Proton VPN apps on your devices or utilizing one of the downloaded configuration files if you wish to set up a manual connection <https://account.protonvpn.com/downloads>.

You sweet sweet summer child... Really, are you pretending to be a child now? **PROTONNNNNN** you are just an embarassment to the tech scene.

> Have a nice weekend!

Yeah thanks for nothing and not even budging a tiny bit on anything.

I even then told them I would tell my users to avoid Proton like the plague because of this ridiculous behavior.
The answer? Basically same thing, reworded.

Guess what?

Well we figured out your authentication (#2878), you unhelpful spineless wonders, so have fun blocking your own users from using your own VPN servers data...

But wait.... this is not even over; A few days later, a [Gluetun user notices paid servers are not part of the Gluetun servers data](https://github.com/qdm12/gluetun/issues/2985).

Because Proton decided to hide away paid servers data from free users. Mind blown 🤯 This is absolutely stupid to its finest extent.

Anyway, I signed in with a paid account, re-updated the servers data. Done. Now your list is public. Congratulations Proton for your security measures, completely useless.

In conclusion... Proton is **unhelpful** and **takes security decisions** that make absolutely no sense.

Please migrate away from Proton whenever you can.

</details>

---