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 HTTP | HTTPS | |
|---|---|---|
| Messages, mail, garage, everything else | yes | yes |
| Notifications with the page closed | no | yes |
| The microphone, so voice notes can be recorded | no | yes |
| Installing it as an app on Android, with its own icon | no | yes |
| “Not secure” in the address bar | shown | not shown |
| The session token | travels in the clear | encrypted |
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.
Which of these is you
Section titled “Which of these is you”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' }, },}Then, once:
- Point that name at the server. On a VPS that is one A record. No domain of your own?
duckdns.orggives you one free in a minute and it never changes. - 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.luaDuckDNS = { 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.
You sit behind the Cfx.re connect proxy
Section titled “You sit behind the Cfx.re connect proxy”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 rent your server from a host
Section titled “You rent your server from a host”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.
A note on the event stream
Section titled “A note on the event stream”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.