Run the same port check from an office line and from a VPN exit, and the two answers can disagree about an address nobody changed. That gap comes from the route and from whichever host replied, not from the service being up or down. Three questions separate those explanations: what the probe actually asked, which host answered it, and what your own server recorded. Work through them in that order and write each answer down before moving to the next.

Fix the Probe Before You Compare Two Runs

A state word is only comparable to another state word when both came out of the same experiment. Five fields pin one down: the source address the far side saw, the resolved destination, the port, the scan type, and the time. Change any of them and you have run a second experiment rather than measured the first one twice.

Check the source before anything else, because a VPN client can be up while traffic still leaves over the physical interface. Ask the far side what it sees, then ask the kernel which interface it chose:

curl -s https://ifconfig.me
ip route get 203.0.113.20

Two addresses on different networks mean the explanation is already in hand, and the rest of these checks will only confirm it. When they agree, capture the probe with the reason flag on, so the state comes back attached to an explanation instead of standing alone as a bare word. The scan type belongs in that record as well, since it describes what was asked rather than what came back. A state with no source, no method and no timestamp cannot be compared with anything:

nmap -sT -p 443,8443 --reason 203.0.113.20

Keep that output beside the record instead of in a scrollback buffer the next person cannot see. Two honest scans that disagree are only an argument when nobody wrote the conditions down. With the conditions fixed, either the results match and the question is closed, or they differ and the difference is a fact about the path.

Find Out Which Host Finished the Handshake

A completed handshake proves that some host completed it, and that host need not be the process you have in mind. A load balancer, a reverse proxy or a SYN proxy can answer before the real server is involved, which is ordinary design rather than a symptom. A valid certificate for the expected name does not narrow this down either, because a certificate describes a service relationship and says nothing about which machine terminated the connection.

The Nmap port-state reference treats this as basic: a state records what a probe could observe from where it was sent, not a property of the port. Read a state word that way and two runs that disagreed start to look consistent rather than contradictory, while the question moves from whether the port is open to who was standing in front of it.

Answer the second question at the backend rather than from outside. A front end that accepts connections and then fails to reach its upstream produces exactly the external picture of a healthy service with a slow application. Only the upstream's own record separates the two cases:

tail -n 5 /var/log/nginx/access.log
ss -ltnp 'sport = :443'

The log line shows the client address the application really saw, and the second command shows whether the process holding port 443 is your application or something in front of it. If those two disagree about which address connected, you are looking at two legs of one request and the ticket should say so.

Check Your Own Rules Before You Blame the Tunnel

Deliberate policy is the most common reason a port answers one network and not another, and it is the least interesting explanation available. Access rules exist to permit one source and drop the rest, so a change of egress is exactly the event they were written for. A different state through a VPN therefore does not show that the provider inspects traffic. It shows that your source address is now different, which is a much smaller claim.

Those rules are readable on the server, so read them instead of speculating about the middle of the network. List the filter rules that mention either address range, then check whether the connection reached the daemon at all:

nft list ruleset | grep -n -e 203.0.113 -e 198.51.100
journalctl -u sshd -n 50 | grep 203.0.113

An empty second result is informative, because nothing reached the daemon and the question belongs to a filter, a security group or the path itself. A hit means the service saw your source and chose its answer, and that decision is usually written down in a rule you can read. Neither outcome supports a story about the provider, and both are cheaper to check than a packet capture.

Read Transport, Certificate and Response Separately

Three layers produce three observations, and a probe that tests one of them cannot report on the other two. Keep them as separate lines in the record rather than folding them into a single verdict, because each line fails in its own way and the failure mode tells you where to look next.

ObservationWhat it settlesWhat it leaves open
Connection completedA host on the path accepted the handshakeWhich host, and whether its upstream is reachable
Certificate valid for the nameThe endpoint holds a credential for that nameWhether the process behind it is the one you meant
Expected response bodyThe application handled the requestAnything about other ports or other paths
Connection refusedA host replied and nothing was listeningWhether a filter sits ahead of the refused port
TimeoutNo reply arrived before the deadlineDrop, blackhole route or an overloaded host

One command produces the first three rows against a service you control. Put its output in the record beside the scan rather than in a chat message:

curl -v --resolve office.example:443:203.0.113.20 https://office.example/

Read the output in order: the handshake answers the transport question, the certificate subject answers the name question, and the response body answers the application question. When the handshake line succeeds and the response line does not, you have a front end with a broken upstream rather than a blocked port. That is a different ticket from the one you opened, and worth saying plainly.

Finish by writing four facts into the ticket: source address, scan type, the host that answered, and the time. Everything a later comparison needs lives in those four, and whoever picks the case up next will not have your terminal history. If you cannot fill all four, the run is not finished.

Аффингæй хæстæг статьялтæ