Skip to content

The certificate

You do not need this to use the phone. Out of the box, with nothing configured, a player scans the QR in Settings › Linked devices and their real phone becomes their character’s phone: messages, mail, contacts, City ID, the garage, properties, the noticeboard, saved places. All of it works over plain HTTP on the port your server already has open.

What a certificate adds is the part a browser will not hand to an insecure page:

Plain HTTPHTTPS
Messages, mail, garage, everything elseyesyes
Notifications with the page closednoyes
The microphone, so voice notes can be recordednoyes
Installing it as an app on Android, with its own iconnoyes
“Not secure” in the address barshownnot shown
The session tokentravels in the clearencrypted

That last row is not cosmetic. On plain HTTP, somebody on the same Wi-Fi as your player can take their session and read their phone. For a roleplay city that may be an acceptable trade; it should at least be a decision rather than a surprise.

You have a name to point at the server — let the phone get its own certificate

Section titled “You have a name to point at the server — let the phone get its own certificate”

This is the short one, and it needs no other program on the machine. The resource asks Let’s Encrypt for a certificate, answers the challenge itself, serves TLS itself and renews itself for as long as it runs.

-- shared/server_config.lua (server-only: this file is never sent to a client)
ServerConfig.Web = {
Url = 'https://phone.yourserver.com/',
Https = {
Enabled = true,
Names = { 'phone.yourserver.com' },
Contact = '[email protected]',
},
}

Then, once:

  1. Point that name at the server. On a VPS that is one A record. No domain of your own? duckdns.org gives you one free in a minute and it never changes.
  2. Let 443 and 80 through the firewall. 80 is only opened while a certificate is being asked for, and closed again as soon as the answer is given — the log says both times.

phonehttps says where the certificate stands. phonehttps check asks Let’s Encrypt’s test server whether it will talk to this machine at all, which is the question worth answering first when nothing arrives: it separates your firewall from this resource.

Renewal takes care of itself, with a third of the certificate’s life still to go, checked every six hours.

If the server is at home, the address moves

Section titled “If the server is at home, the address moves”

A provider hands a home connection a new address whenever it feels like it, and the moment it does, the name points at somebody else’s router: linked phones cannot reach the server and the certificate cannot renew. This resource already notices — it asks for its own address every half hour, for the QR — and on a DuckDNS name it can also put it right:

-- shared/server_config.lua
DuckDNS = {
Enabled = true,
Domains = { 'yourname' }, -- the part in front of .duckdns.org
Token = '01d8b39f-...', -- from your duckdns.org page
},

Told when the address moves, and once a day when it has not — the heartbeat is also what puts things right after a spell with no internet, where the move happened while nothing could be sent. When it works the log says so and no alarm is raised; when it does not, the old warning still goes out, and to the Server webhook with it.

On a VPS the address does not move and none of this is needed. Leave Enabled = false.

Nothing to do. If sv_listingHostOverride is set, the QR is built as https://your.domain/mic_phone/web/ on its own, because that proxy passes the whole root through to your server. This is the easiest case and it is already handled.

You already run a reverse proxy, and would rather keep it that way

Section titled “You already run a reverse proxy, and would rather keep it that way”

One line in shared/config/web.lua:

Url = 'https://phone.yourserver.com/mic_phone/web/',

and a reverse proxy in front that terminates TLS and forwards to the game port. With Caddy that is two lines in a Caddyfile:

phone.yourserver.com {
reverse_proxy 127.0.0.1:30120
}

With nginx, forward / to 127.0.0.1:30120 and turn buffering off (proxy_buffering off;), or the event stream will be held back and the phone will stop hearing anything.

One thing that does not work: putting the orange cloud on a DNS record that points at port 30120. Cloudflare’s proxy only accepts a fixed list of origin ports — 80, 8080, 8880, 2052, 2082, 2086, 2095 and 443, 2053, 2083, 2087, 2096, 8443 — and 30120 is not among them. You need a proxy of your own in front, as above.

You cannot install anything on that machine — but you do not have to: the first option above installs nothing, it is this resource. What you need from your host is two ports of your own, 443 and 80, and a name pointed at the server. Many hosts give you both, some share those ports across every customer on the box and will give you neither. Ask them for the ports before you ask them for anything else; if the answer is no, point a name at a named Cloudflare tunnel instead.

You cannot open a port at all — no name, or your provider is in the way

Section titled “You cannot open a port at all — no name, or your provider is in the way”

A tunnel, then: tools/tunnel.ps1 starts a Cloudflare quick tunnel, writes the address it gets into the config and tells you to restart the resource. No port opened and no certificate.

Know what it is, though: a quick tunnel is ephemeral by design. The address is random and a new one is issued every time the tunnel restarts — and since a browser ties an installed app, its stored token and its push subscription to the address they came from, every one of those has to be set up again each time. It is for trying things out. For anything that has to survive a restart, point a name at a named tunnel, which keeps its address — that needs a Cloudflare account and a domain in it.

The phone keeps one long connection open to hear about messages as they arrive, and it opens that with a POST rather than the usual EventSource GET. Two reasons, both worth knowing if you are putting a proxy in front of it:

  • A token in a query string is a token written into every proxy log between the phone and the server. In a POST body it is not.
  • A Cloudflare quick tunnel holds a GET event-stream shut until the server closes the connection, while letting a POST through (cloudflared#1449). Plenty of proxies buffer GET streams by default.

The server answers a GET on the same routes as well, for anything that only knows EventSource.