The rule says allow the VPN server address to the published service, and it still drops the connection. That outcome is common, and it is not a Docker bug. Two separate matches are wrong: the source you wrote may not be the source the host receives, and the port you wrote may not be the port the filter sees. Fixing both requires looking at the packet path instead of the service diagram.

The myth: allow the VPN server and you are done

A routed VPN can preserve the client tunnel address all the way to the Docker host. A gateway that performs source translation can replace that address with its own. Both designs are legitimate, and they produce different sources for the same user action.

So the phrase the VPN server IP describes an assumption, not an observation. Until you know which of the two designs is in front of you, the rule is a guess.

Source: client pool or translated gateway

Check four things in order. First, the client address pool, for example 10.90.0.0/24. Second, whether the gateway translates that pool before it reaches the host. Third, the source the host actually observes for one new connection. Fourth, whether application authentication distinguishes individual users, because a shared translation address cannot.

If the pool is preserved, a rule matching only the gateway public address misses every client. If the gateway translates deliberately, permitting the translated address admits every user behind it, which may be acceptable for a service with its own login and unacceptable for anything else.

Port: DNAT changes the number you match

For ordinary Linux bridge publishing with Docker iptables, a host mapping from 8443 to container port 443 is translated before the forwarding filter runs. The packet arrives at the filter with destination port 443. A rule that matches 8443 at that point never fires, even though 8443 is what the client typed.

The published port still exists in connection tracking, which is why the original destination can be matched there when you really need it.

Decide which number expresses the intent. Match the post-DNAT container port when the rule protects the service inside the container. Match the original destination with the conntrack option when the rule must describe what the client requested, and accept the extra match cost and rule-order care that come with it.

Chain: DOCKER-USER, not INPUT

Docker's iptables guide places user forwarding policy in the DOCKER-USER chain, which is evaluated before Docker's own forwarding rules. A rule copied from a host service into the INPUT chain filters traffic addressed to the host, not traffic forwarded into a container. That mistake produces a rule that looks right and has no effect on the container path.

Keep the source range, effective destination port, protocol and ingress interface together in the same rule. A source-only accept for every forwarded destination is broader than allowing one published service.

Verify with fresh connections

Test both directions with new connections: one allowed VPN client and one source that should remain denied. Existing connection state can keep a session alive after the policy changed, so an old connection proves nothing about the new rule. Then check the reply path, because the container still has to route back to the source it sees.

The Docker iptables guide documents the chain order and the port translation behavior behind these checks. Read it before copying a recipe, and confirm which backend the installed Docker uses, because the nftables backend integrates policy differently.

Artaigilean co-cheangailte