Arma Reforger's dedicated server uses three UDP ports, and the documentation lists them without saying which ones matter. In practice one is mandatory, one decides whether anyone can find your server, and one should almost never be reachable from the internet at all.

The distinction matters because the failure modes look nothing alike, and only one of them looks like a networking problem.

The three ports

All three are UDP. None of them is TCP, and a firewall rule written for TCP out of habit will pass no game traffic whatsoever while the server appears to start perfectly.

PortPurposeRequired
2001Game traffic (client connections)Yes
17777Steam / A2S query (server browser listing)Only to be findable
19999RCON (remote administration)No, and prefer not

What the config actually says

The server reads a JSON file, conventionally config.json, passed to the binary at launch. The port-relevant part is small:

config.json
{
  "bindAddress": "",
  "bindPort": 2001,
  "publicAddress": "",
  "publicPort": 2001,
  "a2s": {
    "address": "",
    "port": 17777
  }
}

Two pairs, and the difference between them is the thing people get wrong.

bindAddress and bindPort are the socket the server listens on, locally. An empty bindAddress means all interfaces, which is what you want on a dedicated box.

publicAddress and publicPort are what the server tells the backend to advertise. On a machine with a public IP and no NAT they are the same numbers, and leaving them empty lets the server work them out. Behind NAT or inside a container they are not the same thing, and that is where a working server becomes an unreachable one.

The failure modes are not interchangeable

This is the part worth internalising, because it turns a vague "the server is broken" into a specific port to check.

2001 closed. Nobody connects, at all. Direct connect fails, browser connect fails. The server log shows the process healthy and waiting. If any player can connect, 2001 is fine and the problem is elsewhere.

17777 closed. The server runs perfectly and is invisible. It does not appear in the Steam browser, but anyone you give the address to can still connect directly. That asymmetry is the diagnostic: if direct connection works and the browser listing does not, it is the query port, not the game port.

19999 closed. Nothing at all happens, unless you were trying to use RCON. Which is the correct state for it.

Behind NAT or in a container

The two-pair distinction above stops being academic the moment the server is not directly on a public IP.

Inside Docker, bindAddress is on the container's network namespace, not the host's. Publishing the port maps host traffic to it, but the server still advertises whatever publicPort says, and if you have published host 2101 to container 2001, the backend needs to hear 2101 or every browser listing points at a port nothing is listening on.

The rule that avoids the whole class of problem: publicPort must be the port the outside world reaches, and bindPort must be the port the process itself opens. When those are the same number, you can leave both alone. When they are not, only one of them is a guess.

Checking it rather than assuming it

From another machine, not from the server itself. A local test passes through no firewall and tells you nothing:

# Is anything listening locally?
ss -lunp | grep -E '2001|17777'

# Is the query port answering from outside?
nmap -sU -p 17777 your.server.address

UDP scanning is slower and less definitive than TCP, because there is nothing to refuse a connection with: an open port and a silently dropped packet can look the same. Treat open|filtered as inconclusive and confirm from the game itself.

The faster real-world check is the asymmetry above: try a direct connection. If it works, 2001 is open and you are debugging 17777.