http_ssh_proxy is an HTTP forward proxy whose outbound connections travel
through SSH. It forwards ordinary HTTP requests and HTTPS CONNECT tunnels
while preserving request semantics, cancellation, TCP half-close, and bounded
graceful shutdown.
- HTTP forwarding and HTTPS
CONNECTtunneling over one managed SSH transport. - OpenSSH
known_hostsverification or an explicitly pinned SHA256 host-key fingerprint. - Optional TLS and Basic proxy authentication for clients.
- Connection limits, CONNECT idle timeouts, bounded reconnects, and graceful shutdown.
- Strict YAML validation with safe loopback defaults.
- Race-tested critical paths with a 90% repository coverage gate.
- Cross-platform release archives, embedded version metadata, checksums, SPDX SBOM, and GitHub artifact attestations.
- Go 1.26.5 or later when building from source.
- An SSH server that permits TCP forwarding.
- A trusted SSH host key verifier and at least one SSH authentication method.
- A TLS certificate when exposing the proxy beyond the loopback interface.
Copy the example and edit it:
cp config.example.yaml config.yaml# Safe defaults: loopback-only HTTP listener and SSH port 22.
listen_addr: "127.0.0.1:8888"
username: "" # Set both fields or neither
password: ""
proto: "http" # http or https
max_connections: 128
tunnel_idle_timeout_seconds: 300
shutdown_timeout_seconds: 15
ssh_host: "ssh.example.com"
ssh_port: 22
ssh_user: "proxy"
ssh_pass: "" # Password, private key, or both
ssh_key_file: "/home/user/.ssh/id_ed25519"
ssh_reconnect_attempts: 3
ssh_known_hosts_file: "/home/user/.ssh/known_hosts" # Exactly one verifier
# ssh_host_key_fingerprint: "SHA256:..." # Alternative verifier
# Required for proto: https
# pem_path: "./server.pem"
# key_path: "./server.key"The loader rejects unknown fields, multiple YAML documents, invalid addresses and ports, missing SSH authentication, unreadable key files, invalid TLS certificate/key pairs, and ambiguous host-key verification. A non-loopback listener is accepted only with HTTPS and both proxy credentials configured.
Obtain the SSH host key or fingerprint through a trusted channel. Do not accept an unknown key from the same untrusted connection that the proxy will use.
Generate a development certificate without overwriting existing files:
./generate_keys.sh server.key server.pem localhostTLS_DAYS changes its lifetime and TLS_SAN changes the SAN extension. To
deliberately replace both output files, set FORCE=1. Production deployments
should use a certificate issued for the actual proxy hostname.
make build
./http_ssh_proxy -config config.yamlThe Makefile embeds the version from VERSION, the current commit, and the UTC
build time. Inspect a binary or validate configuration without starting the
proxy:
./http_ssh_proxy -version
./http_ssh_proxy -config config.yaml -check-configThe canonical module path also supports installation of a tagged source version:
go install github.com/hightemp/http_ssh_proxy/cmd/http_ssh_proxy@latestRun the automated checks:
make test
make coverage
make check
make fuzzmake check verifies the module graph, runs go vet, enforces at least 90%
statement coverage, and runs govulncheck. CI performs the same module, test,
coverage, fuzz-smoke, and vulnerability checks for every pull request.
Test a running proxy against a target you control. The script reads the listener, TLS CA, and credentials from the config; credentials are placed in a temporary mode-0600 curl config instead of the process arguments:
./test.sh https://example.internal/health config.yamlThe script intentionally has no public default target and never disables TLS verification.
After creating config.yaml next to the installer, run:
./install_service.shThe installer builds a metadata-bearing binary, preserves an existing config,
enforces mode 0600, validates the config as the service account, installs a
hardened unit, and verifies that the service becomes active. The previous
binary is saved as /usr/local/bin/http_ssh_proxy.previous and is restored
automatically if the upgraded service fails to start.
The unit has no Linux capabilities, restricts namespaces, writable filesystem
access, devices, kernel interfaces, address families, task count, and file
descriptors. It intentionally permits read-only home access so a configured SSH
key or known_hosts file can remain in the service user's home directory.
Tagged releases contain these native archives:
| System | Architectures |
|---|---|
| Linux | amd64, arm64, armv7 |
| macOS | amd64, arm64 |
| Windows | amd64, arm64 |
Before publication, GitHub-hosted Linux, macOS, and Windows runners build and
execute -version natively. The release also includes checksums.txt and an
SPDX JSON SBOM. After downloading an archive and the checksum file, verify it:
sha256sum --check checksums.txt --ignore-missing
gh attestation verify http_ssh_proxy_v0.2.0_linux_amd64.tar.gz \
--repo hightemp/http_ssh_proxyBoth build provenance and the SBOM association are attested. Digest validation still matters: attestations establish where an artifact came from, not whether you intended to trust every dependency in it.
Change only VERSION after committing the release contents, then run:
make releaseThe target validates and commits VERSION, refuses an existing local or remote
tag, creates an annotated tag, and atomically pushes the branch and tag without
force. Published tags are therefore immutable in the normal workflow.
If a release went wrong and preserving the same version is essential, first
commit the repair on main, make sure the worktree is clean and VERSION still
matches the affected tag, then use the explicit recovery path:
make rewrite-release-tag CONFIRM_REWRITE_TAG=v0.2.0This command force-updates only that tag, protected by the exact remote tag
object observed immediately before the push (--force-with-lease). It never
force-pushes main. Rewriting a published tag invalidates previous checksums,
cached archives, and users' assumptions; prefer a new patch version whenever
possible.
- This is an HTTP/HTTPS forward proxy, not a SOCKS proxy or a reverse proxy.
- Basic proxy credentials are protected in transit only when the client connects over TLS; plaintext HTTP is restricted to loopback by validation.
- Destination traffic exits from the SSH server and is subject to its DNS, firewall, routing, and audit policy.
- All requests share one managed SSH client; limits prevent unbounded resource use, but one SSH endpoint remains a dependency and bottleneck.
- The project does not currently provide per-user authorization, rate limiting, metrics, or a dedicated health endpoint.
- Protect access to
config.yaml, SSH private keys, TLS private keys, logs, and the service account.
This project is available under the MIT License.