One machine can answer all three needs at once: terminate tunnels, answer HTTPS, and forward between two private LANs. Whether that is sensible comes down to three things you can settle on paper: who owns the public address, who owns each port and protocol pair, and which process is allowed to forward packets. Get those right and the shared host is unremarkable. Get them wrong and you will spend a weekend on a service that starts cleanly and never answers.

What follows is a decision procedure rather than a build sheet. Each step ends with the check that proves the role you just added is genuinely isolated.

Pick the Owner of the Address Before the Ports

A tunnel endpoint and a web server can share a kernel; sharing one address, port and protocol at the same time is what fails. WireGuard usually takes UDP 51820, OpenVPN takes UDP 1194 or TCP 443, and a web server takes TCP 443. The pair that hurts is OpenVPN over TCP 443 beside HTTPS, because both want the same socket and the kernel hands it to whichever bound first. Only a demultiplexer in front, such as sslh or a TLS-aware proxy, lets them share that port, and it adds a component whose failure looks exactly like a firewall problem.

Look at the owners before you change anything: ss -lntup prints the local address, port and process for every listening socket. Two rows showing 0.0.0.0:443 mean the second service never bound, however cheerful its init script was. Bridging moves the owner as well. When eth0 joins br0, the IP moves to br0 and every firewall rule naming eth0 stops matching, so bind services on the bridge and confirm with ip -br addr.

The same ownership rule explains a surprise that turns up with virtual machines. A tunnel server that runs inside a guest on the same LAN keeps the guest's address, so connections aimed at the host's own address stay on the host and the guest's tunnel never sees them, whatever the client profile suggests.

Route Between the Two LANs or NAT One Side

Two LANs that cannot see each other can each dial the public host, which then forwards between the tunnels. That routed design needs three things in place: forwarding switched on, a route pointing at each remote prefix, and a forward policy that lets the transit through. sysctl -w net.ipv4.ip_forward=1 covers the first, ip route add 10.20.0.0/24 dev wg1 the second, and an nftables line like nft add rule inet filter forward iifname "wg0" oifname "wg1" accept the third.

Source NAT is easier to bolt on and harder to debug. Masquerading rewrites the client address, so the far side sees the hub rather than the real host, and its access rules and logs stop being useful for attribution. When both networks are yours, route. When one side belongs to a third party that will not add a return route, NAT is the option that ships this week. Either way, write the address plan down, because two overlapping /24s cannot be routed by any amount of configuration.

Nothing about acting as an internet exit for a laptop prevents the same box from terminating inbound tunnels. Only overlapping addresses do, which is why the address plan is the document that matters.

Test the Split While Two Roles Are Stopped

The useful test is subtraction: stop two roles and confirm the third still answers. A web request should complete with the tunnel service down, and a tunnel handshake should complete with the web server stopped. If both only work when everything runs, you have an accidental dependency, such as a shared firewall table, a stray route, or a name pointing at the wrong address.

Three commands make that test concrete. curl -sk --resolve example.test:443:203.0.113.10 https://example.test/ proves the local listener answers without external DNS, wg show on the client proves the handshake completed, and tcpdump -ni any port 51820 on the hub separates a routing problem from a key problem. A handshake that never appears in the capture died before the host: provider firewall, wrong public address, or a NAT rule on the way.

Repeat the subtraction for the forwarded LANs. With one peer disconnected, a ping across the hub should fail; with it reconnected, the ping should recover without touching a rule. A ping that recovers only after restarting the tunnel points at policy rather than reachability, which is a different fix.

Keep the rule set readable while you subtract roles. nft list ruleset should show one accept rule per direction you actually need, and a forward chain that accepts everything is not separation, it is a working demo.

IPv6-Only Access Lines and IPv4 Services

Some access lines hand out IPv6 reachability only, and services still have to be published over IPv4. The tunnel itself travels over whichever family still passes traffic, while the published service sits on an address inside the tunnel. That inverts the usual assumption and quietly breaks monitoring: a firewall rule or uptime check written for the IPv4 path now watches an interface that carries nothing.

Test each family on its own with curl -4 and curl -6, because one successful request says nothing about the path you did not try. Port ownership does not change across families, but the listener does: ss -lntup lists IPv4 and IPv6 sockets as separate rows, and 0.0.0.0:443 does not cover [::]:443 unless the process asked for a dual-stack socket.

A service that answers on one family and refuses on the other is usually a listener problem, not a routing one. Before calling the design finished, confirm that each role owns its own address or port, that forwarding is enabled and permitted, and that every role still answers with the other two stopped.

مۇناسىۋەتلىك ماقالىلەر