When trying to establish an SSH connection through HTTPS using Nginx as a reverse proxy and proxytunnel as the client-side proxy, many developers encounter the 400 Bad Request error from Nginx. This typically occurs because Nginx isn't properly configured to handle CONNECT requests.
// Sample error from nginx access log
203.xxx.xxx.xxx - - [08/Feb/2012:15:17:39 +1100] "CONNECT localhost:22 HTTP/1.0" 400 173 "-" "-"
SSH over HTTPS requires three main components:
- Client-side proxytunnel configuration
- Nginx reverse proxy server
- Backend SSH server
The key is to properly configure Nginx to handle CONNECT method requests:
server {
listen 443 ssl;
server_name ssh.example.com;
ssl_certificate /etc/ssl/certs/server.crt;
ssl_certificate_key /etc/ssl/private/server.key;
# Special handling for CONNECT method
error_page 418 = @proxy_connect;
if ($request_method = CONNECT) {
return 418;
}
location @proxy_connect {
proxy_pass http://localhost:22;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
# For regular HTTPS traffic
location / {
return 403; # or serve regular content
}
}
Here's the corrected .ssh/config setup:
Host example.com
Hostname example.com
ProxyCommand /usr/bin/proxytunnel -p ssh.example.com:443 -d %h:%p -E -v -H "User-Agent: Mozilla/5.0"
ServerAliveInterval 30
TCPKeepAlive yes
If the above doesn't work, consider these alternatives:
Using socat instead of proxytunnel
ProxyCommand socat - PROXY:ssh.example.com:%h:%p,proxyport=443,proxyauth=user:pass
NGINX Stream Module
For NGINX Plus or compiled with stream module:
stream {
server {
listen 443;
proxy_pass localhost:22;
proxy_protocol on;
}
}
- Verify Nginx has HTTP CONNECT support (compile with --with-http_ssl_module)
- Check firewall rules for both TCP 443 and 22
- Test basic SSH connectivity before adding proxy layer
- Enable debug logging in Nginx: error_log /var/log/nginx/error.log debug;
When implementing this setup:
- Always use SSL with strong ciphers
- Implement rate limiting in Nginx
- Consider client certificate authentication
- Regularly update both Nginx and proxytunnel
When attempting to establish an SSH connection tunneled through HTTPS using Nginx as a reverse proxy, many developers encounter the frustrating 400 Bad Request
error. The core issue lies in how Nginx handles CONNECT requests differently from standard HTTP traffic.
The key error pattern we're seeing:
<-
<- 400 Bad Request
<-
<- 400 Bad Request
<-
nginx/1.0.5
<-
<-
This occurs because Nginx by default doesn't support HTTP CONNECT method required for tunneling. The current configuration attempts to proxy_pass the CONNECT request as regular HTTP traffic.
Here's the correct way to configure Nginx as a TLS termination point for SSH:
server {
listen 443 ssl;
server_name ssh.example.com;
ssl_certificate /etc/ssl/certs/server.crt;
ssl_certificate_key /etc/ssl/private/server.key;
# TCP proxy configuration
location / {
proxy_pass http://127.0.0.1:22;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# These are crucial for SSH over HTTPS
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 1d;
}
}
The client-side ~/.ssh/config
needs these adjustments:
Host example.com
HostName example.com
Port 443
ProxyCommand /usr/bin/proxytunnel -p ssh.example.com:443 -d %h:%p -E -H "User-Agent: Mozilla/5.0"
The key differences from the original:
1. Removed DynamicForward
which isn't needed for basic tunneling
2. Simplified the ProxyCommand line
3. Updated User-Agent string
If you specifically need CONNECT method support, consider this Nginx stream module configuration:
stream {
server {
listen 443;
ssl_preread on;
proxy_pass 127.0.0.1:22;
proxy_protocol on;
}
}
This requires Nginx compiled with --with-stream
and --with-stream_ssl_preread_module
.
To verify the setup:
1. Check Nginx error logs:
tail -f /var/log/nginx/error.log
2. Test with curl first:
curl -vk https://ssh.example.com
3. For detailed proxytunnel debugging:
ssh -vvv user@example.com
Important security measures to implement:
1. Rate limiting:
limit_conn_zone $binary_remote_addr zone=ssh:10m;
limit_conn ssh 3;
2. IP restrictions:
allow 192.168.1.0/24;
deny all;
3. Strong SSL configuration:
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;