7a. Off-site access: the Cloudflare Tunnel add-on#
Authority for this chapter:
docs/production-readiness/REMOTE_ACCESS_V2_RUNBOOK.md
(the operational procedure) and
docs/production-readiness/REMOTE_ACCESS_V2_CLOUDFLARE.md
(why it is built this way). If this chapter and either document disagree,
the runbook wins. For the network-level posture this add-on changes, see
reference: Network and endpoints §2.4;
for a full lookup of every port, config key and mask involved, see
reference: Remote View reference.
This chapter is for plant IT or an installer with a Cloudflare account, doing a second, separate, optional piece of work on top of chapter 7, Ring Remote View. Everything in chapter 7 — MeshCentral, the accounts, the rights masks, the consent prompt, the recordings, the one inbound firewall rule — stays exactly as it is; this chapter only adds a way for someone off the plant LAN to reach that same console. Do this only if the plant has actually decided it wants off-site access — it is not part of a normal install, and skipping it changes nothing else.
1. What it adds and what it does not#
Chapter 7 gives you a MeshCentral console reachable at https://<hmi-pc>:8443/
from inside the plant LAN only. This add-on installs a second Windows
service, cloudflared, on the same HMI PC. It dials out to Cloudflare
and nothing else changes about MeshCentral's reach on the LAN.
| v1 (chapter 7, already installed) | v2 (this chapter) | |
|---|---|---|
| Reach | Plant/office LAN only | Anywhere Cloudflare Access allows, over the internet |
| Direction | — | Outbound only. cloudflared opens a connection to Cloudflare; nothing is opened for Cloudflare, or anyone else, to connect in |
| Inbound firewall rule | One, TCP 8443, LocalSubnet | None. This installer creates no firewall rule at all, on any network adapter |
| Outer gate | — | Cloudflare Access — a login screen at Cloudflare's edge, before the request ever reaches the plant |
| Inner gates | MeshCentral accounts, mandatory two-factor, the view-only/control rights mask, the consent prompt at the panel, session recording | Unchanged. Every one of them still applies behind Access |
| Function | Desktop mirroring of the one live Ring HMI session | The same. Terminal, files, registry and software tabs stay denied by the rights mask, exactly as on the LAN |
What it is not:
- Not a second way into the PLC. It never reads, writes or looks at Ring or the controller — only re-routes how someone reaches the console chapter 7 already installed.
- Not single sign-on. Access and MeshCentral do not share a login: an off-site person authenticates at Access, then logs in to MeshCentral separately (username, password, six-digit code) — twice, every time.
- Not a MeshCentral reinstall. The installer changes five settings in the existing configuration file and restarts the service; it does not re-create the device group, accounts or rights masks.
- Not a way to skip chapter 7's own sign-off, including its pre-ship penetration test — this add-on runs on top of that same server.
Why an outbound tunnel, not a forwarded port. Opening an inbound port to an HMI is exactly the pattern behind real incidents at other plants (exposed remote-desktop and VNC consoles). An outbound-only tunnel opens nothing on the plant side — there is no port to attack from outside, because the connection always starts from inside the plant.
2. Prerequisites and sign-offs#
This gate is bigger than chapter 7's, and it is not a formality: chapter 7 was "people already inside the building can watch the screen"; this chapter is "a named person anywhere on earth can, with the panel's approval, drive a live plant control seat." Confirm all of the following before you touch anything:
- Chapter 7 is installed and verified, including its penetration test, and every account (admin and every operator) has actually enrolled its authenticator app — not just been sent the one-time password.
- The plant, or the company, owns a real domain on Cloudflare — a
zone in a Cloudflare account. The presentation website's own
ring-rs3000.pages.devaddress does not count; you need a domain you control, with a tunnel hostname under it (e.g.remote-hmi.example.com). - Cloudflare Zero Trust is turned on for that account (the free plan covers up to 50 users).
- Outbound TCP 443 to Cloudflare is allowed from the HMI PC, and
ideally outbound UDP 7844 too (faster QUIC; not required —
cloudflaredfalls back to HTTP/2 over 443 without it). Prove this rather than assume it — §4 is exactly that proof. - The source-IP decision has been made — see the box below. Do not start the install until someone has picked one of the two options.
- A log-retention answer. Cloudflare Access's free tier keeps roughly 24 hours of its own access logs; a paid seat (about $7/user/month) keeps 30 days — MeshCentral's own session recordings are unaffected either way and remain the primary evidence.
- A maintenance window. Installing this add-on restarts MeshCentral, dropping any open LAN viewing session (Ring, batching and the controller are untouched) — do it when nobody minds a short gap.
The one decision you cannot put off: who is allowed in, by address#
The MeshCentral install from chapter 7 only accepts connections from the plant/office LAN's own address range. Once this add-on is running, traffic arrives from Cloudflare instead of directly from the LAN, and there is no way to have it both ways — the installer makes you pick one of exactly two options and refuses to guess:
| Choice | Installer flag | What you get | What you give up |
|---|---|---|---|
| Widen the list | -RemoteAllowedIP '203.0.113.0/24','198.51.100.7' |
A real, second address check behind Access — only the named ranges can even reach the login page. The plant LAN range keeps working exactly as before | Only listed ranges work. Someone travelling, on a hotel network or mobile data, is refused even after passing Access |
| Remove the list entirely | -NoUserIpAllowList |
Anyone Access approves can connect from anywhere | MeshCentral no longer checks the caller's address at all — not even for people on the plant LAN. Access becomes the entire perimeter |
There is no third choice, and leaving the address check unchanged "just in case" is not safe: every off-site visitor would be recorded as the HMI PC itself, and one person failing to log in enough times could lock out every other off-site user, since the software can no longer tell them apart by address. Pick widen or remove; do not leave it alone.
Sign-offs (four, one more than chapter 7)#
- Plant POC — approves off-site access to the console specifically, not merely the LAN access chapter 7 already covers.
- Controls engineer — confirms off-site viewing during live batching is acceptable, and states in writing whether control is allowed at all, and for whom.
- IT / security — accepts Cloudflare as the outer gatekeeper and the trade-offs above, and owns keeping the Access policy current.
- Plant management — accepts that the tunnel token is a standing credential in the HMI PC's Windows service configuration, readable by a local administrator (the same class of risk as the operator passwords chapter 7 already accepts), and owns rotating it if the machine is ever in doubt.
Do not install this add-on if: any box above is unticked or any signature is missing; the plant's write-enable cutover is still in progress; the staged smoke test in §4 has not passed on this machine; or nobody has made the address decision above.
3. Cloudflare dashboard steps#
Everything here happens in your browser, in the Cloudflare Zero Trust dashboard, before you touch the HMI PC — the installer cannot see or set any of it.
Create the tunnel, and copy the token into a one-line file#
- Zero Trust dashboard → Networks → Tunnels → Create a tunnel → Cloudflared.
- Name it something the plant will recognise, e.g.
ring-hmi-<sitename>. - On Install and run a connector, choose Windows (x86-64) and find the token — the long block of letters and numbers in the command Cloudflare shows you. Copy only the token, not the whole command line.
- Paste that token, alone, into a plain text file — one line, e.g.
C:\temp\tunnel-token.txt. The installer (§5) reads it from this file and deletes the file the moment it has read it, so the token never sits in your PowerShell history — you only get to use it once. - Do not run the command Cloudflare showed you. The install script
does that step itself, with a version of
cloudflaredalready checked byte-for-byte against Cloudflare's published checksum, and it can undo the whole thing if anything goes wrong — running Cloudflare's own command by hand skips that safety net.
Add the public hostname#
Still in the tunnel you just created: Public Hostname → Add a public hostname. Fill in exactly these values:
| Field | Value | Why |
|---|---|---|
| Subdomain / Domain | remote-hmi . <your domain> |
This exact address is what you give the installer as -Hostname, and what off-site users will type into their browser |
| Path | leave empty | One catch-all rule covers everything the console needs; nothing here needs a per-path rule |
| Service type | HTTPS | MeshCentral's own certificate keeps doing the encrypting between cloudflared and the console; don't switch this to plain HTTP |
| URL | localhost:8443 |
The tunnel runs on the same machine as MeshCentral and reaches it over loopback |
| No TLS Verify | ON | MeshCentral's certificate is self-signed with the machine's own name — this flag lets the tunnel trust it without a real certificate on the HMI PC |
| HTTP Host Header | LEAVE EMPTY | See the warning below |
| Origin Server Name | LEAVE EMPTY | Same reason as above |
Warning — leave those last two fields empty; this is the single most common way this add-on breaks. Filling in either one rewrites the address MeshCentral sees, and it builds part of its browser security policy from that address — so the page loads fine, nothing looks wrong, but the desktop connection silently refuses to start on Connect. The staged smoke test in §4 checks this directly; a failure there means look here first.
On the zone (not the tunnel), turn on Always Use HTTPS — without it a plain-HTTP request can still reach the tunnel, and MeshCentral will redirect it somewhere that doesn't work (there is no plain listener behind it at all).
Put Cloudflare Access in front — this step is not optional#
Skipping this step publishes the MeshCentral login page to the entire internet. Zero Trust → Access → Applications → Add an application → Self-hosted:
| Field | Value |
|---|---|
| Application domain | remote-hmi.<your domain> — the whole hostname, no path |
| Session duration | Short. Eight hours or less for viewers; one hour or less for anyone who can control the console |
| Policy: action | Allow |
| Policy: include | The named email addresses of the people approved for off-site access, or a group from your own identity provider — not "everyone in the company" unless IT has explicitly signed off on that |
| Policy: require | MFA — your identity provider's own multi-factor step, or at minimum a one-time PIN |
Add a second policy that blocks the path /agent.ashx if your plan
supports path-scoped rules — nothing legitimate ever reaches it from
outside the plant, since the one Mesh Agent that uses it is local; this is
belt-and-braces on top of what MeshCentral already refuses.
Write down the tunnel name, hostname, Access application name, policy name, session duration and approved-email list with the plant's cutover paperwork. From here on the Access policy needs the same attention as a password — review it whenever someone leaves.
4. Prove it first: the staged smoke#
Before this add-on ever touches a real tunnel token or domain, run the script that proves the idea works, using a free, throwaway Cloudflare address that needs no account:
cd <copied>\remote-view
.\Test-RemoteViewTunnelSmoke.ps1
What it does. It brings up a temporary public address in front of the same MeshCentral console chapter 7 already installed, runs a fixed set of checks against it, and then — no matter what happened — shuts that tunnel back down before the script exits. Nothing about it survives the script finishing, and no real domain or real tunnel token is involved.
Warning — exposure window. While this script runs, the MeshCentral login page really is reachable on that temporary public address, with no Cloudflare Access in front of it yet. Only chapter 7's own gates protect it during that window: account passwords, mandatory two-factor, and the consent prompt at the panel. Don't run this on a machine where any account is still sitting on its one-time password.
What a pass looks like. On the bench, this script has already run
successfully: the staged smoke passed with no warnings or failures, in
about a minute. See remote-view\dist\smoke-<timestamp>.log, or
§9.7 of the reference chapter,
for the actual run evidence. It confirmed, among other things, that the temporary
address resolved and served the MeshCentral login page; that the address
the browser connects to survives the hop rather than being silently
rewritten (the exact failure the two empty fields in
§3 exist to prevent); that every underlying
remote-desktop channel came up correctly; and, deliberately, that an
unrecognised caller was refused by the console's own security check.
Two things it handles for you. It waits up to about ninety seconds for the temporary address to become reachable before it starts checking anything, and if Cloudflare's own network answers with a temporary "still starting up" error on the first check, it quietly retries for up to a minute before giving up. The whole run is budgeted at four minutes by default; you shouldn't need to change that.
Run this once on the bench, and again on the actual HMI PC before you run the real installer in §5 — a pass on one machine does not prove the plant's own network allows the outbound connection this add-on depends on.
5. Install on the HMI PC#
Elevated PowerShell on the HMI PC, from the same remote-view\ folder used
for chapter 7. Pick one of the two forms below, matching your decision
from §2:
# Widen the address list to the approved off-site ranges:
.\Install-RemoteViewTunnel.ps1 `
-TunnelTokenFile C:\temp\tunnel-token.txt `
-Hostname remote-hmi.example.com `
-RemoteAllowedIP '203.0.113.0/24','198.51.100.7'
# ...or, if off-site users roam and there is no fixed address range:
.\Install-RemoteViewTunnel.ps1 `
-TunnelTokenFile C:\temp\tunnel-token.txt `
-Hostname remote-hmi.example.com `
-NoUserIpAllowList
If the HMI PC has no internet access, download cloudflared-windows-amd64.exe
on another machine and add -PayloadPath <path> to the command above — it
is still checked byte-for-byte against the published checksum either way.
What the installer does, in order:
- Checks chapter 7 is actually healthy first — refuses to run at all unless elevated, on a 64-bit machine, with the MeshCentral service installed, running, and actually answering on 8443.
- Downloads and verifies
cloudflared, checks its checksum against the one Cloudflare published, and stops with both checksums printed if they don't match. - Installs it as a Windows service using the tunnel token, turns its own auto-update off (the same rule chapter 7 applies to MeshCentral and the local agent, so a validated HMI PC doesn't quietly take new code), and waits for real evidence the tunnel actually connected to Cloudflare — not just that the service says it's running.
- Changes five settings in MeshCentral's configuration file, after saving a timestamped backup first, then restarts MeshCentral and confirms it answers on 8443 again. One of the five is the list of hostnames the browser may say it came from; chapter 7's own install already builds that list correctly (the real hostname, lowercase, with a dot) as part of its certificate fix, so this step widens it to add the public hostname rather than creating it from nothing.
- Confirms nothing was opened inbound — no firewall rule of any kind
now mentions
cloudflared, on any adapter, because none should exist. - Writes a small record file of exactly what it changed, so re-running the installer with the same settings does nothing extra, and so uninstalling later (§8) knows exactly what to put back.
Rollback. If anything in that sequence fails, the installer undoes it all in reverse order — including restoring the configuration backup and restarting MeshCentral — before reporting the failure. A failed run leaves chapter 7's install exactly as it was before you started.
6. The full off-site check#
Do this from a browser that is genuinely off-site — someone's home connection, or a phone on mobile data, not the plant's own Wi-Fi. Write down what actually happened at every step, with the date and who tested it, and keep that with the plant's cutover paperwork. Every check below matters (this condenses all eighteen checks from the runbook's own table); do not skip any of them.
- Browse to
https://remote-hmi.<your domain>/. The Cloudflare Access login must appear first. If MeshCentral's login page appears instead with no Access challenge, stop — the Access application is missing or pointed at the wrong hostname. - Log in at Access with MFA — you should land on the MeshCentral login page next.
- Try Access with an email not on the approved list — refused at Cloudflare's edge; the plant network never sees the attempt.
- Log in to MeshCentral as an operator, then enter the six-digit code — the console loads completely. A blank screen, or a browser error mentioning an invalid origin, means the public hostname is missing from the allowed-hostname list (§5).
- Open a desktop session — you see the live HMI screen. Watch the browser's own console for a security-policy warning while you do; that warning means the ingress rewrote the address, exactly what the two empty fields in §3 prevent.
- Confirm the consent prompt fires at the panel and names the remote account. Deny it once, on purpose, and confirm the session does not start.
- Try to drive from a view-only account (screen mirrors, input is silently discarded), then from a control account (works, only after the panel consents).
- Confirm the Terminal and Files tabs are simply absent for both account kinds, same as on the LAN.
- Check the audit log (My Server → Server Log, or the account's own login history) — it must show the real off-site public address, not the HMI PC's own loopback address. This is the one check that proves the address-forwarding setting is actually taking effect.
- If you widened the address list: connect from outside the approved ranges — refused before you even reach a login screen. If you removed the address list instead: confirm in writing that everyone understands the plant LAN itself no longer has an address check either.
- Check
meshcentral-recordings\for a new recording from the off-site session. - Try to reach
/agent.ashxthrough the tunnel from outside — refused. - Confirm the local Mesh Agent still shows Online in the Plant HMI group after the restart in §5.
- Hold a desktop session open for at least thirty minutes. A long tunnel can reconnect on its own; if it does, a new consent prompt appears at the panel, which correctly denies it unattended. Tell the shift this can happen so a dropped session isn't reported as a fault.
- If the tester's own connection changes address mid-session (Wi-Fi to mobile data), expect to be asked to log in again — deliberate, not a bug.
- Confirm an ordinary office PC still reaches
https://<hmi-pc>:8443/exactly as before, with its certificate trust untouched.
Any line that behaves differently from what is written above is a stop. Nobody is told off-site access is ready until every check above has been recorded.
7. Who may drive from off-site#
- Off-site accounts default to view-only. Granting one actual control needs the controls engineer's named approval, recorded by person and reason — the exception, never the default.
- "Who's driving" is worse when the driver isn't in the building. Chapter 7's etiquette rules apply here too, plus one addition: an off-site driver announces by phone or radio before taking control, every time.
- The panel always wins. An operator at the HMI who denies the prompt wins, no matter who is asking from off-site.
- Access logins expire on purpose. Keep the session duration in §3 short — a browser tab left open on someone's home computer must not be a standing way in.
- Removing someone off-site is a two-place job. Take them out of the Access policy and MeshCentral's own user list — either alone leaves a half-closed door. Recordings already made are not deleted when an account is removed; they stay as evidence.
8. Uninstall and the two dashboard actions the script cannot do#
.\Install-RemoteViewTunnel.ps1 -Uninstall # asks for confirmation
.\Install-RemoteViewTunnel.ps1 -Uninstall -Force # skips the prompt
This removes the cloudflared service and its files, puts back the five
configuration settings from §5 — each to what it
was before the install, or removed entirely if it wasn't there before —
restarts MeshCentral, and confirms it still answers on 8443. Chapter 7's
install is unaffected: LAN access on 8443 keeps working throughout, and
Ring, its settings and the controller are never touched.
If one of the five settings has been changed by hand since the install ran, the uninstaller notices, leaves it alone, and names it in a warning rather than silently overwriting a deliberate later edit.
Two things this script cannot do — go back to the Cloudflare dashboard and do them by hand:
- Delete or disable the tunnel itself in the Zero Trust dashboard — removing the connector from the HMI PC does not remove the tunnel, its DNS record, or its Access policy from Cloudflare's side.
- Revoke the tunnel token if the HMI PC is retired, replaced, or ever in doubt — this kills the tunnel immediately, no matter what is still installed on the machine.
To confirm the plant is genuinely back to LAN-only: browse to the console from off-site and confirm nothing answers, and check with plant IT that the tunnel and Access application are gone from Cloudflare too, not just the HMI PC.
9. Troubleshooting map#
| Symptom | Most likely cause | Where to look |
|---|---|---|
| Every off-site user is refused before they even see a login page | The address list was not widened, or only one of its two copies was | §2, §5 |
| Access login works, MeshCentral loads blank, console mentions an invalid origin | The public hostname is missing from the allowed-hostname list | §5 |
| Pages load fine but the desktop session never starts; a security-policy warning shows | The tunnel's ingress is rewriting the address MeshCentral sees | §3 — the two fields must stay empty; §4 |
The cloudflared service will not stay running |
Wrong or already-used token, or outbound 443 is blocked | §2; the installer prints the exact failure line |
| The local Mesh Agent goes offline after install | settings.aliasPort was set, or LANonly was turned off, by hand |
docs/production-readiness/REMOTE_ACCESS_V2_RUNBOOK.md §4B–4C |
| LAN users get certificate errors, or can no longer connect | settings.cert was changed, or strictTransportSecurity was set, by hand |
docs/production-readiness/REMOTE_ACCESS_V2_RUNBOOK.md §4B–4C |
| Off-site users are logged out more than expected | Their own connection is changing address (roaming, network switch) | §6, item 15 |
| Off-site sessions drop, and a consent prompt appears with nobody answering | A tunnel reconnect raised a new prompt, which correctly failed closed | §6, item 14 |
| Audit log shows the HMI PC's own address for off-site logins | The address-forwarding setting is not taking effect | docs/production-readiness/REMOTE_ACCESS_V2_RUNBOOK.md §4A, §4C |
10. Simpler alternative: Tailscale#
This chapter assumes the plant wants a public hostname behind Cloudflare with email-based sign-in — right once there are more than a handful of off-site users, or when people should connect with nothing but a browser.
If the plant network cannot allow outbound traffic to Cloudflare, nobody
wants a domain and an Access policy, or there are six or fewer off-site
users, use Tailscale instead: a mesh VPN, free for up to six users,
with no domain, no Access configuration, and no third party decrypting and
re-encrypting the traffic in the middle. It installs on the HMI PC and on
every off-site person's own device, who then browse to the same
https://<hmi-pc>:8443/ address chapter 7 already set up, over that
private network instead. Either way, the idea is identical: outbound only,
authenticated, nothing inbound, MeshCentral unchanged.
11. Status#
As of this writing:
- The staged smoke test in §4 has passed on the bench (2026-09-04), after a fix to the test script itself — an earlier run had checked the temporary address before Cloudflare finished making it reachable, and failed for that reason alone. With the fix, the staged smoke passed with no warnings or failures — see §9.7 of the reference chapter for the actual run evidence.
cloudflareditself has been downloaded and its checksum confirmed to match the one pinned in the installer — version 2026.7.3.- The real installer,
Install-RemoteViewTunnel.ps1, has NOT yet been run against a real Cloudflare tunnel — that needs an actual Zero Trust tunnel token and a real domain, which the bench verification did not have. - Still outstanding, each one a hard gate before this add-on goes live at any site: the Cloudflare Access policy has not been built and tested; the full off-site check in §6 has not been run from a genuinely off-site connection; and the additional penetration test this add-on requires (the v2 runbook's own §8) has not been run.
Treat "the scripts exist and pass their own bench test" and "this add-on has passed its own sign-off gates at this plant" as two different questions — check the runbook's own checklist, not this page.
Sources verified#
remote-view/README.mdremote-view/Install-RemoteView.ps1— parameter block; the hostname/certificate-name/allowedOriginblock; theINSTALL OKsummaryremote-view/Provision-RemoteView.ps1— parameter blockremote-view/Uninstall-RemoteView.ps1— parameter blockremote-view/New-RemoteViewFirewallRule.ps1remote-view/Install-RemoteViewTunnel.ps1— parameter block and.DESCRIPTIONremote-view/Test-RemoteViewTunnelSmoke.ps1—.SYNOPSIS/.DESCRIPTIONremote-view/config-template.jsondocs/production-readiness/REMOTE_ACCESS_RUNBOOK.mddocs/production-readiness/REMOTE_ACCESS_V2_RUNBOOK.mddocs/production-readiness/REMOTE_ACCESS_V2_CLOUDFLARE.md
The status claims in §11 reflect the bench verification session recorded 2026-09-04; check the v2 runbook's own checklist for the current state before relying on this page.