Remote Access to PLCs and Modbus Devices via VPS

Quick answer

Once a VPS-based VPN tunnel connects head office to a site’s network (see VPS as a SCADA VPN Concentrator), reaching a PLC remotely is mostly a routing problem, not a special protocol problem: engineering software and Modbus polling both work over the tunnel exactly as they would on a local network, because the PLC is simply now reachable as if you were on-site, without that device ever being directly exposed to the internet.

The problem this solves

A PLC at a remote site needs occasional engineering access (reprogramming, diagnostics) and often continuous data access (a central SCADA or historian polling its values). Neither of these should mean opening port 502 to the internet, for the reason covered in detail below: Modbus has no authentication at all, and a PLC reachable on the open internet is genuinely, immediately at risk, not theoretically. The VPN tunnel from the linked concentrator guide solves the access problem; this page covers what you actually do once it’s in place.

Why Modbus specifically needs this protection

Modbus TCP operates on port 502 with no username, no password, no encryption, and no concept of authorisation levels: a device that accepts a correctly formatted Modbus command executes it, regardless of who sent it or whether they should be allowed to. This isn’t a configuration oversight you can fix with a setting, it’s how the protocol was specified, decades before internet-facing industrial equipment was a realistic threat model. The Modbus Security Protocol Specification (adding TLS to Modbus/TCP) exists, but most devices in the field today don’t support it, which is exactly why network-level protection (a VPN tunnel, not a protocol-level fix) is the practical answer for almost every real deployment.

Reaching engineering software remotely

Most PLC programming software (ladder logic editors, configuration tools) is designed to connect over a local network, often expecting the engineering workstation and the PLC to be on the same subnet. Through a site-to-site VPN tunnel, this works the same way it would locally, since the tunnel makes the remote site’s network reachable as if you were physically present: point the engineering software at the PLC’s normal local IP address, routed through the tunnel rather than a local switch.

For software that’s fussier about network topology, or for full remote desktop control of an engineering workstation already on-site, a lightweight remote desktop tool (or a jump host VM on the same VPN, covered in Multi-Site Industrial IoT Data Aggregation) running over the same tunnel is the common pattern.

Polling Modbus data centrally

A central SCADA system or historian polling Modbus registers across multiple sites is the most common ongoing use of this kind of access. The pattern: the polling software connects to each site’s PLC at its local IP address (10.10.0.x or 192.168.x.x, whichever the tunnel routes), exactly as if it were local, with the VPN tunnel handling the actual cross-site routing invisibly to the application. A typical Modbus read, using a library like pymodbus, looks like this regardless of whether the device is local or reached through a tunnel:

from pymodbus.client import ModbusTcpClient

client = ModbusTcpClient('10.10.0.50', port=502)
client.connect()
result = client.read_holding_registers(0, 10)
print(result.registers)
client.close()

The Modbus call itself never knows or cares that it’s going through a VPN tunnel rather than a local cable, which is exactly the point: the security is handled at the network layer, leaving the application layer unchanged.

Read access vs write access: a deliberate distinction worth making

It’s worth treating “read this PLC’s data” and “write commands to this PLC” as genuinely different permission levels, even though Modbus itself doesn’t enforce this distinction natively. In practice this means: a historian or dashboard polling data only needs read access and should be configured (and ideally network-segmented) to only ever issue read function codes, while write access for genuine control changes should be restricted to specific, deliberate engineering sessions, not left running continuously from automated systems unless that’s a specific, intended part of the control architecture.

Modbus vs DNP3: a quick note

Modbus dominates general industrial automation and building-scale systems. DNP3 (commonly on port 20000) is more common in utility-scale SCADA, particularly electricity distribution and similar critical infrastructure, and includes more built-in support for features like time-stamped events that matter at that scale. The VPN-tunnel approach in this guide applies identically to both: neither protocol has meaningful native security, and both rely on network-level protection in practice.

Putting it together: a realistic access pattern

Access need How it’s handled
Continuous data polling (read-only) Central historian/SCADA polls over the VPN tunnel, read function codes only
Occasional engineering access Engineer connects via VPN, programming software targets the PLC’s local IP as normal
Alerting on threshold values Polled data feeds into Node-RED or a rule engine, see the Modbus-to-MQTT gateway guide
Direct internet exposure of port 502 Never. This is the pattern this entire page exists to avoid.

Troubleshooting common problems

Engineering software can’t find the PLC over the tunnel, despite the connection being up. Many PLC programming tools use broadcast or auto-discovery on the local subnet by default, which doesn’t traverse a VPN tunnel the way a direct IP connection does. Most software supports specifying the PLC’s IP address directly instead of relying on discovery; this is almost always the fix when discovery-based connection fails over a remote link.

Modbus reads succeed but return unexpected values. Almost always a register-mapping issue, not a connectivity problem: confirm you’re reading the correct register type (holding registers vs input registers vs coils are genuinely different address spaces in Modbus, easy to confuse) against the specific PLC manufacturer’s documented register map.

Intermittent read failures under load. Many older PLCs accept only one or a very limited number of simultaneous Modbus connections; if multiple systems are polling the same device at once, contention can cause this. Routing all reads through a single concentrator process, as covered in the gateway guide linked below, avoids this by design rather than fighting it after the fact.

Write commands silently have no effect. Check the targeted register or coil address is actually writable on that specific PLC model, since some addresses are read-only at the device level regardless of what the Modbus protocol itself would technically permit, and a write to a read-only address is sometimes acknowledged without an error despite doing nothing.

Keeping a record of who accessed what

Because Modbus itself has no concept of user identity, any meaningful audit trail has to happen at the layer above it. A practical approach: log VPN tunnel connections (WireGuard’s handshake timestamps, covered in the concentrator guide) alongside engineering-software session logs where available, giving a reasonable picture of who connected when, even though the Modbus traffic itself remains anonymous to the protocol. For environments where this matters for compliance, a jump host that all engineering access must pass through, with its own login logging, is a stronger pattern than direct device access even over a VPN.

A note on legacy serial Modbus (RTU)

Older PLCs sometimes only speak Modbus RTU over a physical serial connection (RS-485), not Modbus TCP. A serial-to-Ethernet converter bridges this onto the same network the VPN tunnel reaches, after which the rest of this guide applies the same way; the converter itself becomes the device the tunnel and Modbus TCP client both interact with. Worth checking which protocol variant a specific PLC actually speaks before assuming TCP access is possible without this extra piece of hardware.

Frequently asked questions

Can I just use a firewall rule allowing only my office IP to reach port 502, instead of a VPN?

It’s better than nothing, but weaker than a VPN: IP-based allowlisting doesn’t encrypt the traffic, doesn’t survive your office IP changing, and doesn’t stop someone who’s compromised a device with an allowed IP. A VPN tunnel covers all three of those gaps.

Does this approach add noticeable latency to Modbus polling?

WireGuard’s overhead is small enough that it’s rarely the limiting factor; the polling interval you choose matters far more for overall data freshness than the few milliseconds of VPN overhead.

What if the PLC itself supports a built-in VPN client?

Some newer industrial routers and gateways do, and that’s a valid alternative to routing through a separate VPS concentrator for smaller setups. The VPS-hub pattern in this guide becomes more valuable specifically once you’re managing several sites centrally, where one hub is simpler than configuring each device individually.

Where do I send this data once I’m reading it reliably?

See Building a Modbus-to-MQTT Gateway with a VPS for turning raw Modbus polling into the same MQTT-based pattern used throughout the rest of this site.

Is it safe to give a third-party contractor temporary remote access to a PLC this way?

It can be, with deliberate limits: a time-boxed WireGuard peer added for the duration of the work and removed immediately afterward, ideally restricted via AllowedIPs to only the specific device they need, rather than the whole site network. Treat temporary access with at least as much care as permanent access, since it’s easy to forget to revoke.

A quick reference: Modbus function codes worth knowing

Understanding a handful of the most common Modbus function codes makes both troubleshooting and access-control decisions more concrete, rather than treating Modbus as an opaque black box.

Function code Name Read or write
0x01 Read Coils Read
0x03 Read Holding Registers Read
0x04 Read Input Registers Read
0x05 Write Single Coil Write
0x06 Write Single Register Write
0x10 Write Multiple Registers Write

Worth noting again that Modbus itself doesn’t restrict which function codes a given connection can use, any client able to reach the device on port 502 can issue any of these regardless of intent. This is exactly why the read/write distinction discussed earlier has to be enforced by the systems and processes around the protocol, not by Modbus itself.

Working with multiple PLC vendors on the same network

Mixed-vendor sites (a mix of Siemens, Allen-Bradley, Schneider and others, for example) are common in practice, and worth a brief note: while Modbus TCP itself is a standardised protocol that works the same way regardless of vendor, each manufacturer’s specific register map, scaling conventions and occasionally proprietary extensions differ. Central polling and remote access infrastructure (the VPN tunnel, the polling client) works identically across vendors; only the specific register addresses and interpretation differ, which is why keeping accurate, vendor-specific documentation per device matters more than any single generic “Modbus guide” can fully provide.

Can I rate-limit Modbus polling to protect an older, fragile PLC?

Yes, this is generally handled at the polling client level (the interval set in Node-RED, a SCADA system, or a custom script) rather than the PLC itself, since Modbus has no built-in rate-limiting concept. Starting with a conservative polling interval and shortening it only if needed is the safer direction to iterate in.

Do I need a different approach for safety-critical control versus monitoring?

Generally yes. Remote write access for genuine control changes deserves more deliberate, restricted access patterns (specific time windows, specific authorised engineers, more careful logging) than read-only monitoring, which carries much lower risk even if something goes wrong with the connection itself.

How do I find out which Modbus registers a specific PLC actually exposes?

The manufacturer’s own documentation for that specific model is the authoritative source, often called a register map or Modbus map in the manual. Generic Modbus references explain the protocol mechanics but can’t tell you what a specific device has mapped to a specific address, since that’s entirely manufacturer and model-specific.

Is there a way to test Modbus connectivity without affecting the live PLC?

Reading registers (function codes 0x01, 0x03, 0x04) is inherently non-disruptive to the PLC’s own operation, since it only retrieves current values without changing anything. Testing connectivity with a simple read, as shown in the pymodbus example earlier in this guide, is safe to do against a live, in-service PLC.

Can I monitor multiple PLCs from one central polling system reliably?

Yes, this is the normal pattern for any site with more than a handful of devices, with the central system maintaining a list of device IP addresses and register maps, polling each in sequence or in parallel depending on the tooling used and how much load the network and devices can comfortably handle.