1. Background
Recently, I deployed a Gitea service on a physical server using the 1Panel management panel and Docker.
Accessing the service via the domain name on ports 80/443 in a web browser worked perfectly. However, when I tried to push code using Git from my local terminal, I ran into a series of errors.
Initially, the errors looked like Git key or permission issues. But after systematically investigating layer by layer down the network transport layer, I discovered it was a classic case of network isolation and port mapping problems.
2. TL;DR
This is not a Git authentication issue; it's purely a TCP connectivity problem.
When deploying non-HTTP services with Docker, such as:
- SSH port
22 - MySQL port
3306 - Gitea's custom SSH port
If the host port of the container is bound only to 127.0.0.1, direct requests from the public internet cannot reach that port.
You need to change the listening address to 0.0.0.0, or simply omit the bind IP in the Docker port mapping, to allow external network access.
3. Troubleshooting Step by Step
3.1 First Layer: The Illusion of Permissions
- Symptom: When pushing with the default command, the terminal would hang or prompt for a password.
- Misconception: I thought my local SSH public/private keys weren't configured correctly.
- Breakthrough: After specifying the custom port
222, the real underlying network error surfaced.
3.2 Second Layer: Connection timed out
After explicitly specifying the port, Git showed the following error:
ssh: connect to host example.com port 222: Connection timed out
Connection timed out means the data packet received no response, usually indicating it was blocked en route.
Common causes include:
- The cloud server's security group hasn't allowed the port
- Server firewalls like UFW or iptables are blocking it
- The CDN doesn't support that TCP port
- The domain is behind a proxy platform like Cloudflare
- Restrictions from the ISP or network egress
After replacing the domain with the server's actual public IP, the timeout issue was bypassed.
This also proves: if a domain uses a CDN like Cloudflare, its default proxy usually only supports certain web ports and won't automatically proxy SSH or other generic TCP services.
3.3 Third Layer: Connection refused
After bypassing the CDN, the error changed from a timeout to:
ssh: connect to host 152.32.191.114 port 222: Connection refused
Connection refused means the data packet reached the server, but the target port isn't listening correctly for external connections.
After checking the Docker port mapping on the server, I found Gitea's SSH port was bound as:
127.0.0.1:222->22/tcp
This means:
- The host machine can access port
222internally - The public internet cannot access port
222 - External requests reaching the server are immediately rejected
127.0.0.1 is the local loopback address, which only accepts requests from within the server itself.
3.4 Final Breakthrough: Removing the Local Bind
I modified the Docker Compose configuration, changing:
ports:
- "127.0.0.1:222:22"
to:
ports:
- "222:22"
You can also write it explicitly as:
ports:
- "0.0.0.0:222:22"
If you're using 1Panel, you can also enable "External Port Access" in the container's port settings.
After rebuilding the container and testing again, the Git SSH connection was restored, and code could be pushed normally.
4. Key Takeaways
4.1 Understanding TCP Error Messages
Connection timed out
Indicates the data packet received no response en route.
Priority checks:
- Cloud server security group
- UFW or iptables
- CDN proxy status
- Domain DNS resolution
- Correct public IP
- ISP network restrictions
Connection refused
Indicates the data packet reached the target server, but no service is listening on the target port, or the service isn't listening on the public network interface.
Priority checks:
- Is the service process running?
- Is the Docker container running?
- Is the port mapping correct?
- Is the service listening address
0.0.0.0? - Is the port only bound to
127.0.0.1?
5. The Difference Between 127.0.0.1 and 0.0.0.0
5.1 127.0.0.1: Local Access Only
127.0.0.1 is the local loopback address.
Binding to this address means:
- Only the host machine can access it internally
- It does not accept public internet requests
- Suitable for databases, internal APIs, and other services that shouldn't be exposed to the public internet
Think of it as "operating behind closed doors."
5.2 0.0.0.0: Listen on All Interfaces
0.0.0.0 means listening on all network interfaces of the server.
Binding to this address means:
- It can accept local requests
- It can accept LAN requests
- After the firewall allows it, it can accept public internet requests
Think of it as "listening with the door open."
Binding a port to
0.0.0.0expands the service's network exposure. Therefore, you must also configure security groups, firewalls, and authentication.
6. The Difference Between Reverse Proxy and Client Direct Connection
6.1 Why Could the Web Interface Be Accessed Normally?
Because 1Panel typically uses OpenResty or Nginx to listen on the public ports 80 and 443.
The request flow is:
Browser
↓ HTTPS 443
OpenResty / Nginx
↓ Internal Forwarding
Gitea Web Container
Even if the Gitea web port is only bound to 127.0.0.1, Nginx can still access it from within the server.
6.2 Why Must Git SSH Have a Separate Open Port?
Git SSH uses the SSH protocol, which doesn't go through a standard HTTP reverse proxy.
The request flow is:
Local Git Client
↓ SSH TCP 222
Server Public Port
↓ Docker Port Mapping
Gitea Container Port 22
The local terminal must directly access the server's TCP port.
If that port is only bound to 127.0.0.1, the public client cannot establish a connection.
7. Troubleshooting Flow Summary
When a Docker service is inaccessible from the public internet, follow this order to troubleshoot:
- Check if the domain resolves to the correct public IP.
- Bypass the CDN and test directly with the public IP.
- Check if the cloud server's security group allows the port.
- Check the local firewall, such as UFW or iptables.
- Check if the Docker container is running correctly.
- Check the Docker port mapping.
- Check if the port is bound to
127.0.0.1or0.0.0.0. - Check if the service inside the container is actually listening on the target port.
- Finally, check SSH keys, user permissions, and repository permissions.
8. Summary
When troubleshooting network issues, don't just focus on application-layer configurations.
Errors can superficially appear as:
- Git permission anomalies
- Invalid SSH keys
- Password authentication failures
- No write permission to the repository
But the real problem might be at a lower layer, the TCP transport layer.
Only by combining information from:
- Error types
- DNS and CDN
- Cloud server security groups
- System firewalls
- Docker port mappings
- Service listening addresses
can you accurately pinpoint which layer the data packet is breaking at.
Understanding the difference between Connection timed out and Connection refused, as well as the roles of 127.0.0.1 and 0.0.0.0, is the most fundamental and important step in troubleshooting Docker network problems.