Repository navigation
v2.1.0
Upgrading from 2.0 (behaviour changes)
Authentication stays optional and off by default; 2.1 only warns when the proxy is open. These
defaults changed:
- Image tags.
latestandlogging-disablednow move only on releases, and pushes to
mainpublish:main. Pin:2,:2.1or:2.1.0to choose how far you follow. - Log level. The Docker image logs at
infoinstead ofdebug, and connection log lines
have a new format. Set-e LOGGING_LEVEL=debugto restore the old level. - Loopback and link-local destinations. Proxying to loopback, link-local (including the
cloud metadata service at169.254.169.254) and unspecified addresses is refused with reply
0x02. Set-e SOCKS5_ALLOW_LOOPBACK=trueto restore it. Containers on Docker's default
bridge network are unaffected. - Error log file. The image no longer writes
/app/errors.log. Set
-e SOCKS5_LOG_FILE=/app/errors.logto restore it. - Empty credentials. An empty
SOCKS5_USERNAMEorSOCKS5_PASSWORDcan no longer be used
to log in. There is no opt-out. - Keep 2.0 entirely. Pin
jcaponigro20/simple-socks5:2.0.0(or
:2.0.0-logging-disabled).
Added
- Versioned Docker image tags: a
vX.Y.Zrelease publishesX.Y.Z,X.YandX(and
X.Y.Z-logging-disabled), so a deployment can pin a version. A pre-release such as
v2.1.0-rc.1publishes only its exact version. - Startup warning when the proxy runs without authentication on a non-loopback host: anyone
who can reach the port can use it. SetSOCKS5_AUTH_REQUIRED=falseto acknowledge an
intentional no-auth setup; this logs one INFO line instead. Visible at-L debug,info
andwarning. - Startup warning when the built-in default credentials are in use. The password is never
logged. - README
Securitysection: open-proxy risk, requiring authentication, publishing only on
trusted interfaces, cleartext credentials, and pinning:2.0.0for the pre-2.1 behaviour. SOCKS5_HANDSHAKE_TIMEOUTandSOCKS5_CONNECT_TIMEOUT(seconds, default10). An invalid
value exits at startup with an error.SOCKS5_MAX_CONNECTIONS(default200): the maximum number of concurrent client
connections. It must be a positive integer; an invalid value exits at startup with an error.SOCKS5_LOG_FILE: when set, ERROR and CRITICAL lines are also written to this file,
rotated at 1 MB with 5 backups.SOCKS5_HEALTHCHECK_PORT(default1080): the port the Docker healthcheck probes. Set it
when you run the image with a different--port.SOCKS5_ALLOW_LOOPBACK(defaultfalse): set totrueto allow destinations on loopback,
link-local and unspecified addresses, which are now refused by default.
Changed
latestandlogging-disablednow move only on stable releases; pushes tomainpublish
:mainonly. An image is published only after the tests and a container smoke test pass.- README Quick Start leads with an authenticated example published on
127.0.0.1; the
no-auth example is labelled for trusted networks only. - Docker image base is now
python:3.13-slim, pinned by digest (Python 3.10 reaches end of life
in October 2026). The image also setsPYTHONUNBUFFERED=1andPYTHONDONTWRITEBYTECODE=1. - Behaviour change: the Docker image now logs at
infoby default instead ofdebug, so it
no longer logs every relayed chunk. Set-e LOGGING_LEVEL=debugto restore the old output.
This also applies when you override the container command without-L. - The Docker image runs
pythondirectly as PID 1 (exec-formCMD), so it receives
docker stop's SIGTERM. app.pyreads theLOGGING_LEVELenvironment variable when-L/--logging-levelisn't
given. Precedence is-L, thenLOGGING_LEVEL, thendebug; an invalid value exits with
an error.- TCP relay buffer raised from 4 KiB to 64 KiB, about 7× the throughput. The UDP relay's
receive buffer, which shares the setting, grows to match. - The "connection limit reached" warning is logged at most once every 10 seconds, with the
number of connections rejected since the previous one, instead of once per rejection. - No reverse-DNS (PTR) lookups: CONNECT to an IP address no longer waits up to 2 seconds for
one, and visited addresses no longer leak to the resolver. Logs show the hostname the client
sent or the IP address. - Connection log lines have a new format, which matters if you parse them. Each connection
logsCONNECTION | client -> destinationwhen it opens and one summary line when it
closes,CLOSED | client -> destination | up=N B down=N B | N.NN s, instead of a debug line
per relayed chunk. - A client disconnecting mid-handshake or mid-relay is logged at
debugwithout a traceback.
So is a client that offers no acceptable authentication method, which was a warning. - Log colours are used only when the output is a terminal.
- Behaviour change: errors are no longer written to
errors.login the working directory
(/app/errors.login the Docker image). SetSOCKS5_LOG_FILEto keep a log file. - The Docker
HEALTHCHECKrunspython -m src.healthcheck, which speaks SOCKS5 instead of
opening and closing a bare TCP connection.
Fixed
- SIGTERM and SIGINT now shut the server down gracefully: it stops accepting connections, gives
in-flight connections up to 5 seconds to finish, closes the rest and exits with code 0. This
includes connections accepted just before the signal. An open UDP association can take the
full 5 seconds.docker stoppreviously waited 10 seconds and killed the container (exit
code 137). - README: the default port is
1080, not9999; removed the claim of configurable
connection limits; the authentication example now setsSOCKS5_AUTH_REQUIRED=true. - The outbound socket leaked when a CONNECT target refused the connection; it is now closed
before the refusal reply is sent. - Downloads were truncated when the client read slower than the origin sent: a full send
buffer dropped the connection. The relay now waits for the slow side (up to 5 minutes per
write), so the transfer completes. - TCP half-close: a client or server that closes its sending side still receives the reply.
Before, the first end-of-stream from either side closed the whole connection. - Handshake slowloris: a client that connected and then sent nothing (or one byte at a time)
held a connection slot forever, so 200 idle connections locked out every client. The
greeting, authentication and request must now arrive withinSOCKS5_HANDSHAKE_TIMEOUT
seconds in total; a client that is still sending is disconnected without a reply. The
separate 45-second authentication timeout is gone. - Connection bursts: the listen backlog was 5, so on Linux clients connecting during a burst
waited 1 to 3 seconds for TCP retransmits before the proxy accepted them. It is now the
system maximum (SOMAXCONN). - Outbound connect timeout: CONNECT to an unreachable host waited for the OS default (75 s on
macOS, about 2 minutes on Linux) and replied0x01. It now gives up after
SOCKS5_CONNECT_TIMEOUTseconds and replies0x04(host unreachable). - Accurate SOCKS reply codes (RFC 1928): network unreachable replies
0x03, host unreachable
and connect timeouts0x04, an unknown address type0x08, and a domain name that isn't
valid UTF-80x04. These all replied0x01before. - A malformed UDP datagram (too short, a non-zero reserved field, an unknown address type or a
truncated address) ended the UDP association and sent a stray failure reply on its control
connection; a single spoofed datagram was enough. It is now dropped and the association keeps
working. A datagram to a destination that can't be sent to (such as port 0) is dropped too. - A request now gets exactly one SOCKS reply. An error after the success reply (for example
during a CONNECT tunnel) used to send a second, failure reply, injecting 10 bytes into the
stream; it is now only logged. - A UDP association now logs a
CLOSEDline when it ends, like a TCP connection, with the
datagram payload bytes relayed each way. - A UDP association now ends as soon as its TCP control connection closes, as RFC 1928
requires. Before, its relay port kept relaying datagrams for up to 2 minutes after the client
disconnected. The 2-minute idle timeout stays, but only datagrams from the client reset it. - The Docker healthcheck no longer logs an error with a traceback every 30 seconds (about 2,880
a day); the proxy now logs nothing abovedebugfor it. - Error log rotation: each module had its own handler writing to the same
errors.log, so
rotation renamed the file under the others and could lose or interleave lines. - An exception that escaped a connection handler was printed to stderr by
socketserver,
even with-L disabled. It now goes through logging, so-Lapplies: ERROR with its
traceback, ordebugfor a client that disconnected. - An empty domain name is rejected with
0x04(host unreachable). It used to connect to the
proxy host itself.
Security
- Usernames and passwords are compared in constant time, and both are always checked, so
response timing no longer reveals which one was wrong. - Stricter RFC 1929 parsing: credentials that aren't valid UTF-8 now get a failure reply
instead of an uncaught error and a traceback on stderr, and an empty username or password
is always rejected. Behaviour change: an emptySOCKS5_USERNAMEorSOCKS5_PASSWORD
can no longer be used to log in. - A failed login logs the client's IP address instead of the username it submitted.
SOCKS5_USERNAMEandSOCKS5_PASSWORDare read for each login rather than once at import.- Behaviour change: proxying to loopback (
127.0.0.0/8,::1), link-local
(169.254.0.0/16, including the cloud metadata service at169.254.169.254, and
fe80::/10) and unspecified (0.0.0.0/8,::) addresses is refused by default, including
their IPv4-mapped, IPv4-compatible and NAT64 forms. The resolved IP is checked, so
localhostis refused too. A refused CONNECT gets0x02and a WARNING, logged at most every
10 seconds; a refused UDP datagram is dropped. Private ranges stay allowed. Docker users on
the default bridge network are unaffected; if you proxy tolocalhoston the host or with
--network host, setSOCKS5_ALLOW_LOOPBACK=trueto restore the old behaviour.