modbus-proxy-rs is a Modbus TCP layer 7 reverse proxy. It lets multiple
clients talk to Modbus devices that only support a single client, or a very
small number of concurrent clients.
- Download and extract the latest release for your platform.
- Create
modbus-config.yml:
devices:
- modbus:
url: plc1.acme.org:502
listen:
bind: 0.0.0.0:9000- Run
./modbus-proxy-rs -c ./modbus-config.ymland point your clients tolocalhost:9000.
This repo is a fork of tiagocoutinho/modbus-proxy-rs that I tried to improve purely via agentic coding (all gemini 3.1 pro medium thinking, with a few "extend", "test", "review", "fix/improve" iterations).
Note:
The changes in this fork have only been verified via manual and automatic testing. I did not read the code myself. Use at your own risk.
Changes in this fork:
- Safer proxying with transaction ID checks, backend timeouts, reconnects, and better error logging.
- New Test Suite for mismatches, timeouts, and multiplexed concurrency.
- More detailed connection and traffic logs.
- Project polish: release workflow, and local devcontainer/editor setup, license
- Multiple client connections can share one backend Modbus TCP connection per configured device.
- Requests to the same backend are serialized on a first-come, first-served request/reply basis to avoid cross-talk between clients.
- Backend connections are opened lazily, reused while clients are active, and closed again when the last client disconnects.
- Backend replies are validated against the request transaction ID before they are forwarded to the client.
- Backend reads use a 5 second timeout so a stalled device does not block the proxy forever.
- Backend errors are logged and the proxy reconnects on the next request.
- Multiple configured devices run concurrently, each with its own listener and backend connection state.
Download the archive that matches your platform from the latest release:
modbus-proxy-rs-x86_64-pc-windows-msvc.zip: 64-bit Windowsmodbus-proxy-rs-x86_64-unknown-linux-gnu.tar.gz: most 64-bit Linux systemsmodbus-proxy-rs-x86_64-unknown-linux-musl.tar.gz: static 64-bit Linux buildmodbus-proxy-rs-aarch64-unknown-linux-gnu.tar.gz: ARM64 Linuxmodbus-proxy-rs-armv7-unknown-linux-gnueabihf.tar.gz: 32-bit ARM Linux
Each release also includes SHA256SUMS.txt for checksum verification.
On Linux:
tar -xzf modbus-proxy-rs-x86_64-unknown-linux-gnu.tar.gz
cd modbus-proxy-rs-x86_64-unknown-linux-gnu
./modbus-proxy-rs -c ./modbus-config.ymlOn Windows PowerShell:
Expand-Archive .\modbus-proxy-rs-x86_64-pc-windows-msvc.zip -DestinationPath .
cd .\modbus-proxy-rs-x86_64-pc-windows-msvc
.\modbus-proxy-rs.exe -c .\modbus-config.ymlInstallation requrires the rust toolchain.
Install from crates.io:
cargo install modbus-proxy-rsOr build locally:
cargo build --releaseThe proxy reads its configuration from a file passed with -c /
--config-file.
Each device entry needs:
modbus.url: the backend Modbus TCP device addresslisten.bind: the local listening address clients should connect to
Configuration files can be written in YAML, TOML, or JSON.
Example in YAML:
devices:
- modbus:
url: plc1.acme.org:502
listen:
bind: 0.0.0.0:9000Example in TOML:
[[devices]]
modbus.url = "plc1.acme.org:502"
listen.bind = "0.0.0.0:9000"Example in JSON:
{
"devices": [
{
"modbus": { "url": "plc1.acme.org:502" },
"listen": { "bind": "0.0.0.0:9000" }
}
]
}You can expose more than one backend by adding more entries:
devices:
- modbus:
url: plc1.acme.org:502
listen:
bind: 0.0.0.0:9000
- modbus:
url: plc2.acme.org:502
listen:
bind: 0.0.0.0:9001Start the proxy with:
modbus-proxy-rs -c ./modbus-config.ymlClients should then connect to the configured listen.bind address instead of
directly to the Modbus device.
The server shuts down cleanly on Ctrl+C, and on Unix also reacts to
SIGTERM.
Logging is controlled with RUST_LOG.
RUST_LOG=info modbus-proxy-rs -c ./modbus-config.ymlUseful levels:
warn: backend connection problems and retriesinfo: client connect/disconnect events and request/reply sizesdebug: connection lifecycle and per-request frame dumpstrace: raw backend write/read frame dumps
This project ships with a Dockerfile.
Build the image:
docker build -t modbus-proxy .The final image is a minimal scratch image that runs as a non-root user and
starts with:
./modbus-proxy-rs -c /etc/modbus-proxy.ymlIf you have a local config.yml:
devices:
- modbus:
url: plc1.acme.org:502
listen:
bind: 0.0.0.0:502Run the container with:
docker run --init --rm -p 5020:502 -v $PWD/config.yml:/etc/modbus-proxy.yml modbus-proxyOr pass a different configuration path:
docker run --init --rm -p 5020:502 -v $PWD/config.yml:/config.yml modbus-proxy -c /config.ymlClients can then connect to <your-hostname-or-ip>:5020.
If you configure multiple devices, publish each corresponding bind port with an
additional -p <host port>:<container port> mapping.
For a ready-to-use cloud environment, open the repository in https://github.dev/dominikandreas/modbus-proxy-rs, connect to a codespace (ctr + shift + p, 'codespaces')
and choose the default dev container.
The container already includes the Rust
toolchain and VS Code integration, so you can start hacking and run cargo test right away.
For local containerized development, open the repo in VS Code and select
Dev Containers: Reopen in Container. This uses the checked-in
.devcontainer/ configuration and gives you the same setup locally.
Run the test suite with:
cargo testThe repository also includes:
- a
.devcontainer/setup for containerized development .vscode/launch.jsonand.vscode/tasks.jsonfor local debugging/tasks
- Tiago Coutinho [email protected]
From which this repository is forked. He implemented the original core logic, I only vibe coded on top.