Serial port proxy to TCP or TELNET
Project description
Ser2tcp
Simple proxy for connecting over TCP, TELNET, SSL, WebSocket or Unix socket to serial port
https://github.com/cortexm/ser2tcp
Features
- can serve multiple serial ports using pyserial library
- each serial port can have multiple servers
- server can use TCP, TELNET, SSL, WebSocket or SOCKET protocol
- TCP protocol just bridge whole RAW serial stream to TCP
- TELNET protocol will send every character immediately and not wait for ENTER, it is useful to use standard
telnetas serial terminal - SSL protocol provides encrypted TCP connection with optional mutual TLS (mTLS) client certificate verification
- WebSocket protocol connects through the HTTP server with binary frames for data and JSON text frames for signal control
- SOCKET protocol uses Unix domain socket for local IPC
- servers accepts multiple connections at one time
- each connected client can sent to serial port
- serial port send received data to all connected clients
- non-blocking send with configurable timeout and buffer limit
- serial signal control (RTS, DTR, CTS, DSR, RI, CD) via escape protocol or WebSocket JSON
- IP filtering with allow/deny lists (CIDR notation supported)
- built-in HTTP server with REST API for status monitoring
- web interface for viewing configured ports and connections
- web terminal clients (xterm.js VT100 terminal and raw colored view)
- authentication with session management and API tokens
- light/dark mode web UI (follows system preference)
Installation
pip install ser2tcp
or from source:
pip install .
Uninstall
pip uninstall ser2tcp
Command line options
-h, --help show this help message and exit
-V, --version show program's version number and exit
-v, --verbose Increase verbosity
-u, --usb List USB serial devices and exit
--hash-password PASSWORD
Hash password for config file and exit
-c CONFIG, --config CONFIG
configuration in JSON format (default: ~/.config/ser2tcp/config.json)
If no config file is specified and default config doesn't exist, creates one with HTTP server on first free port from 20080.
Verbose
- By default print only ERROR and WARNING messages
-v: will print INFO messages-vv: print also DEBUG messages
Configuration file example
{
"ports": [
{
"serial": {
"port": "/dev/ttyUSB0",
"baudrate": 115200,
"parity": "NONE",
"stopbits": "ONE"
},
"servers": [
{
"address": "127.0.0.1",
"port": 10001,
"protocol": "tcp"
},
{
"address": "0.0.0.0",
"port": 10002,
"protocol": "telnet",
"send_timeout": 5.0,
"buffer_limit": 65536
}
]
}
]
}
Legacy format (JSON array at root level) is still supported for backward compatibility.
Serial configuration
serial structure pass all parameters to serial.Serial constructor from pyserial library, this allows full control of the serial port.
USB device matching
Instead of specifying port directly, you can use match to find device by USB attributes:
{
"serial": {
"match": {
"vid": "0x303A",
"pid": "0x4001",
"serial_number": "dcda0c2004bc0000"
},
"baudrate": 115200
}
}
Use ser2tcp --usb to list available USB devices with their attributes:
$ ser2tcp --usb
/dev/cu.usbmodem1101
vid: 0x303A
pid: 0x4001
serial_number: dcda0c2004bc0000
manufacturer: Espressif Systems
product: Espressif Device
location: 1-1
Match attributes: vid, pid, serial_number, manufacturer, product, location, description, hwid
- Wildcard
*supported (e.g."product": "CP210*") - Matching is case-insensitive
- Error if multiple devices match the criteria
- Device is resolved when client connects, not at startup (device does not need to exist at startup)
baudrateis optional (default 9600, CDC devices ignore it)
Server configuration
| Parameter | Description | Default |
|---|---|---|
address |
Bind address (IP for tcp/telnet/ssl, path for socket) | required* |
port |
TCP port (not used for socket/websocket) | required* |
protocol |
tcp, telnet, ssl, websocket or socket |
required |
endpoint |
WebSocket URL path (websocket only), must be unique | required* |
token |
Per-server auth token (websocket only) | - |
ssl |
SSL configuration (required for ssl protocol) |
- |
data |
Forward serial data (default true), false = control-only |
true |
control |
Signal control configuration | - |
send_timeout |
Disconnect client if data cannot be sent within this time (seconds) | 5.0 |
buffer_limit |
Maximum send buffer size per client (bytes), null for unlimited |
null |
max_connections |
Maximum clients per server (0 = unlimited) | 0 |
* address/port required for tcp/telnet/ssl; address for socket; endpoint for websocket
Port-level connection limit
You can also limit total connections across all servers on a port:
{
"ports": [{
"max_connections": 10,
"serial": {"port": "/dev/ttyUSB0"},
"servers": [
{"protocol": "tcp", "address": "0.0.0.0", "port": 10001, "max_connections": 5},
{"protocol": "websocket", "endpoint": "device"}
]
}]
}
- Port-level
max_connections: limits total clients across all servers (default 0 = unlimited) - Server-level
max_connections: limits clients on that specific server (default 0 = unlimited) - Both limits are checked — if either is reached, new connections are rejected
WebSocket configuration
WebSocket connections go through the HTTP server — no separate listening port needed:
{
"protocol": "websocket",
"endpoint": "my-device",
"control": {
"rts": true,
"signals": ["rts", "dtr", "cts", "dsr"]
}
}
- Accessible at
ws://host:port/ws/my-device(orwss://for HTTPS) - Available on all configured HTTP servers
- Binary frames carry raw serial data (bidirectional)
- Text frames carry JSON control messages:
{"rts": true},{"signals": {...}} - Signal state sent automatically on connect, then only on change
- Auth: per-server
token, global user session, or both accepted - Web terminals available at
/xterm/<endpoint>(VT100) and/raw/<endpoint>(colored hex)
Socket configuration
For socket protocol, address is the path to the Unix domain socket:
{
"address": "/tmp/ser2tcp.sock",
"protocol": "socket"
}
- Socket file is created on startup and removed on shutdown
- If socket file already exists, it is replaced
- Connect with:
socat - UNIX-CONNECT:/tmp/ser2tcp.sock - Not available on Windows
SSL configuration
For ssl protocol, add ssl object with certificate paths:
{
"address": "0.0.0.0",
"port": 10003,
"protocol": "ssl",
"ssl": {
"certfile": "/path/to/server.crt",
"keyfile": "/path/to/server.key",
"ca_certs": "/path/to/ca.crt"
}
}
| Parameter | Description | Required |
|---|---|---|
certfile |
Server certificate (PEM) | yes |
keyfile |
Server private key (PEM) | yes |
ca_certs |
CA certificate for client verification (mTLS) | no |
If ca_certs is specified, clients must provide a valid certificate signed by the CA.
IP filtering
Restrict client connections by IP address using allow and/or deny lists:
{
"address": "0.0.0.0",
"port": 10001,
"protocol": "tcp",
"allow": ["192.168.1.0/24", "10.0.0.5"],
"deny": ["192.168.1.100"]
}
| Parameter | Description |
|---|---|
allow |
List of allowed IP addresses/networks (CIDR notation supported) |
deny |
List of denied IP addresses/networks (CIDR notation supported) |
Filter logic:
- No config: all IPs allowed
- Only
deny: all IPs allowed except those in deny list - Only
allow: only IPs in allow list are allowed - Both: deny takes precedence, then allow list is checked
Works on TCP, TELNET, SSL, WebSocket and HTTP servers. Not applicable to Unix socket (no IP addresses). Rejected connections are logged.
Creating self-signed certificates
Generate CA and server certificate for testing:
# Create CA key and certificate
openssl genrsa -out ca.key 2048
openssl req -new -x509 -days 365 -key ca.key -out ca.crt -subj "/CN=ser2tcp CA" \
-addext "basicConstraints=critical,CA:TRUE" \
-addext "keyUsage=critical,keyCertSign,cRLSign"
# Create server key and certificate signing request
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr -subj "/CN=localhost"
# Sign server certificate with CA
openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt
# For certificate bound to specific domain/IP (SAN - Subject Alternative Name):
openssl req -new -key server.key -out server.csr -subj "/CN=myserver.example.com" -addext "subjectAltName=DNS:myserver.example.com,DNS:localhost,IP:192.168.1.100"
openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -copy_extensions copy
# Clean up CSR
rm server.csr
For mTLS (mutual TLS with client certificates):
# Create client key and certificate
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr -subj "/CN=client"
openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt
rm client.csr
Testing SSL connection:
# Without client certificate
openssl s_client -connect localhost:10003
# With client certificate (mTLS)
openssl s_client -connect localhost:10003 -cert client.crt -key client.key
HTTP server and API
Optional HTTP server for monitoring and management:
{
"http": [
{"name": "main", "address": "0.0.0.0", "port": 8080}
]
}
name: optional label for the server (displayed in web UI Settings tab)- HTTP servers can be added/removed/modified via web UI without restart
With authentication (configured at root level, shared across all HTTP servers):
{
"http": [
{"address": "0.0.0.0", "port": 8080}
],
"users": [
{"login": "admin", "password": "sha256:...", "admin": true}
],
"tokens": [
{"token": "my-api-key", "name": "monitoring", "admin": false}
],
"session_timeout": 3600
}
users: login credentials with optionaladminflag and per-usersession_timeouttokens: permanent API tokens for automation (no expiration)session_timeout: global default session timeout in seconds- First user added (via CLI or web UI) is automatically admin
- Cannot delete last admin (user or token) — at least one admin must exist
Generate password hash:
ser2tcp --hash-password mysecretpassword
HTTPS with SSL:
{
"http": [
{"address": "0.0.0.0", "port": 8080},
{"address": "0.0.0.0", "port": 8443, "ssl": {
"certfile": "server.crt", "keyfile": "server.key"
}}
]
}
With IP filtering:
{
"http": [{
"address": "0.0.0.0",
"port": 8080,
"allow": ["192.168.0.0/16"],
"deny": ["192.168.1.100"]
}]
}
API endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/login |
no | Authenticate, returns session token |
| POST | /api/logout |
no | Invalidate session |
| GET | /api/status |
yes | Runtime status (serial ports, servers, connections) |
| GET | /api/detect |
yes | Available serial ports with USB/device attributes |
| GET | /api/signals |
yes | Signal states for all ports |
| GET | /api/settings |
yes | Get settings (http servers, session_timeout) |
| DELETE | /api/ports/<p>/connections/<s>/<c> |
yes | Disconnect client |
| POST | /api/ports |
admin | Add new port configuration |
| PUT | /api/ports/<index> |
admin | Update port configuration |
| DELETE | /api/ports/<index> |
admin | Delete port configuration |
| PUT | /api/ports/<index>/signals |
admin | Set RTS/DTR signals |
| GET | /api/users |
admin | List users |
| POST | /api/users |
admin | Add user |
| PUT | /api/users/<login> |
admin | Update user |
| DELETE | /api/users/<login> |
admin | Delete user |
| GET | /api/tokens |
admin | List API tokens |
| POST | /api/tokens |
admin | Add API token |
| PUT | /api/tokens/<token> |
admin | Update API token |
| DELETE | /api/tokens/<token> |
admin | Delete API token |
| PUT | /api/settings |
admin | Update session_timeout |
| POST | /api/settings/http |
admin | Add HTTP server |
| PUT | /api/settings/http/<index> |
admin | Update HTTP server |
| DELETE | /api/settings/http/<index> |
admin | Delete HTTP server |
| GET | /xterm/<endpoint> |
no | WebSocket VT100 terminal |
| GET | /raw/<endpoint> |
no | WebSocket raw terminal |
Auth levels: no = public, yes = any authenticated user, admin = admin user/token only.
Authentication: Authorization: Bearer <token> header or ?token=<token> query parameter. Without users/tokens configured, all endpoints are accessible without authentication.
Usage examples
ser2tcp -c ser2tcp.conf
Direct running from repository:
python run.py -c ser2tcp.conf
Connecting using telnet
telnet localhost 10002
(to exit telnet press CTRL + ] and type quit)
Installation as service
Linux - systemd user service
- Copy service file:
cp ser2tcp.service ~/.config/systemd/user/ - Configuration file will be created automatically at
~/.config/ser2tcp/config.jsonon first run - Reload user systemd services:
systemctl --user daemon-reload - Start and enable service:
systemctl --user enable --now ser2tcp - To allow user services running after boot you need to enable linger (if this is not configured, then service will start after user login and stop after logout):
sudo loginctl enable-linger $USER
Linux - systemd system service
- Create system user:
sudo useradd -r -s /usr/sbin/nologin -G dialout ser2tcp - Copy service file:
sudo cp ser2tcp-system.service /etc/systemd/system/ser2tcp.service - Create configuration file
/etc/ser2tcp.conf - Reload systemd and start service:
sudo systemctl daemon-reload sudo systemctl enable --now ser2tcp
Useful commands
# Check status
systemctl --user status ser2tcp
# View logs
journalctl --user-unit ser2tcp -e
# Restart
systemctl --user restart ser2tcp
# Stop
systemctl --user stop ser2tcp
For system service, use sudo systemctl instead of systemctl --user.
Requirements
- Python 3.8+
- pyserial 3.0+
- uhttp-server 2.3.2+ (for HTTP/API and WebSocket)
Running on
- Linux
- macOS
- Windows
Credits
(c) 2016-2026 by Pavel Revak
Support
- Basic support is free over GitHub issues.
- Professional support is available over email: Pavel Revak.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ser2tcp-3.0.1.tar.gz.
File metadata
- Download URL: ser2tcp-3.0.1.tar.gz
- Upload date:
- Size: 78.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1d9552c67e5834e85638b2c3a17efd053b43190104240d40e3bdfe09cfc6bc37
|
|
| MD5 |
b1351049f24e26c0f55b57cecbc4ac8f
|
|
| BLAKE2b-256 |
65f3d2ae8a75cec944e7cae741445ab3aef2d398971eac30a3e247869efd46bf
|
Provenance
The following attestation bundles were made for ser2tcp-3.0.1.tar.gz:
Publisher:
publish.yml on cortexm/ser2tcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ser2tcp-3.0.1.tar.gz -
Subject digest:
1d9552c67e5834e85638b2c3a17efd053b43190104240d40e3bdfe09cfc6bc37 - Sigstore transparency entry: 1346771870
- Sigstore integration time:
-
Permalink:
cortexm/ser2tcp@1386573d8c8da49f15ff388899cb7bf7af45eb4a -
Branch / Tag:
refs/tags/v3.0.1 - Owner: https://github.com/cortexm
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1386573d8c8da49f15ff388899cb7bf7af45eb4a -
Trigger Event:
release
-
Statement type:
File details
Details for the file ser2tcp-3.0.1-py3-none-any.whl.
File metadata
- Download URL: ser2tcp-3.0.1-py3-none-any.whl
- Upload date:
- Size: 66.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
29a9cc9af8de675ed7dac9f3fd27260654a4d043813c595c8e5a5ace47cb1110
|
|
| MD5 |
aff9a2e0ec7f4657c0442c1ff2893144
|
|
| BLAKE2b-256 |
fcb3f392b2c6923d2982c03de5381d15b456586e1a2f869af6c82ea800fc8226
|
Provenance
The following attestation bundles were made for ser2tcp-3.0.1-py3-none-any.whl:
Publisher:
publish.yml on cortexm/ser2tcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ser2tcp-3.0.1-py3-none-any.whl -
Subject digest:
29a9cc9af8de675ed7dac9f3fd27260654a4d043813c595c8e5a5ace47cb1110 - Sigstore transparency entry: 1346771943
- Sigstore integration time:
-
Permalink:
cortexm/ser2tcp@1386573d8c8da49f15ff388899cb7bf7af45eb4a -
Branch / Tag:
refs/tags/v3.0.1 - Owner: https://github.com/cortexm
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1386573d8c8da49f15ff388899cb7bf7af45eb4a -
Trigger Event:
release
-
Statement type: