HTTPS and access
HTTPS is what the phone camera requires, and it's the part of a home setup with the most moving pieces: a certificate, a name, and a route to the box. This page covers the failures specific to that. The setup itself, and which TLS option fits your situation, is in Install.
The phone camera is blocked
Browsers only allow the camera (barcode scanning, photos) in a secure context,
which means HTTPS or localhost. No setting in Cobblr changes this. It's a
browser rule.
- On the box itself,
http://localhost:8088counts as secure, so scanning works there. - From a phone, you need HTTPS. Turn it on with one of the options in Install. Tailscale is the recommended one because it gives every device a real certificate with nothing exposed.
- HTTPS on and the camera still won't open? Confirm the address bar shows
https://with no certificate warning. - On the offline-CA option the certificate has to be trusted on the device, not only installed. On iOS that is the extra toggle under Settings, General, About, Certificate Trust Settings.
Over a plain http://192.168.x.x LAN address the camera silently doesn't open.
Caddy crash-loops on first run
The HTTPS proxy (caddy) exits on a bad configuration instead of serving without
TLS. docker compose logs caddy shows the reason.
- Unknown TLS mode. A line like
unknown COBBLR_TLS_MODE '...'means the value isn't one ofcloudflare,internal,tsnet, or the legacyduckdns. Fix the spelling in.env. The builder sets this for you. - A Caddyfile parse error on a bare
emailline. An older image with a blankCOBBLR_ACME_EMAIL. Either pull the current one (docker compose pull) or setCOBBLR_ACME_EMAILto any address you control.
Why the blank email broke older images
The ACME modes carry an optional contact email. When COBBLR_ACME_EMAIL was
blank, an older image rendered a bare email line that Caddy rejected as a
parse error, and the container crash-looped. Current images self-heal by
dropping the empty line, and the builder fills the field from your email.
Tailscale (tsnet) sits there and never comes up
Nothing is broken. It just hasn't been authorized yet: on the own-hostname option the proxy joins your tailnet as its own node, and by default it waits for you to approve it.
-
Find the one-time sign-in link in its logs:
docker compose logs caddy | grep -iE 'to authenticate|login.tailscale' -
Open the
https://login.tailscale.com/...link that prints, sign in, and approve the new node. The address you set inCOBBLR_SITE_ADDRESSstarts answering right after, and the approval is one-time. -
Once the node is up, find it in the Tailscale admin console and disable key expiry on it, or it signs itself out in a few months and you get the same wait again.
- No such line appears? Follow the logs live (
docker compose logs -f caddy) while the container starts, since the link prints early. - Would rather the box never wait on a link? Put a reusable auth key from
the admin console in
TS_AUTHKEYand it joins on its own. That is the optional headless path, not a requirement. No auth key is needed otherwise.
The browser says SSL_ERROR_INTERNAL_ERROR_ALERT (Tailscale)
The node is up and the proxy is running, but no certificate was ever issued, so
there is nothing to hand the browser. Firefox renders that as
SSL_ERROR_INTERNAL_ERROR_ALERT, other browsers as a generic secure-connection
failure, and openssl s_client as tlsv1 alert internal error with
no peer certificate available.
Almost always the address is not a name this node owns. Tailscale issues a
certificate only for the node's own full name, and the proxy registers using
only the first part of COBBLR_SITE_ADDRESS, so a wrong tailnet leaves the
node Connected and healthy-looking while the certificate request is for a
name you do not have.
- Find the machine in the admin console
and compare its full name to
COBBLR_SITE_ADDRESS, character for character. - Check for a duplicate. If an earlier run left a
cobblrnode behind, this one joined ascobblr-1, and the name you configured belongs to the older node. - Confirm MagicDNS and HTTPS certificates are both on, on the DNS page. Without them no certificate can be issued for any name.
Correcting only the tailnet part keeps the same node, so docker compose up -d caddy is the whole fix with nothing to approve again. Changing the first
part creates a different node and prints a fresh approval link, which is
expected rather than a sign of trouble.
The certificate didn't issue (DNS challenge)
The Cloudflare mode (and the retired DuckDNS mode, on older installs) gets a real
certificate through a DNS challenge. When the challenge fails,
docker compose logs caddy names it. The usual causes:
- A wrong
CLOUDFLARE_API_TOKEN(orDUCKDNS_TOKENon a legacy setup). The Cloudflare token must be scoped Zone, DNS, Edit for your zone. - No outbound internet from the box, so it can't reach Let's Encrypt.
- An expired certificate months in? Check
docker compose logs caddyfor a failing challenge and re-issue the DNS token. A token you revoke or rotate breaks the renewal silently. - Set
COBBLR_ACME_EMAILto get an expiry warning email from Let's Encrypt before that day.
How caching and renewal work
Once a certificate issues it's cached under ./data/caddy, so restarts never
re-request one. Renewal is the exception: certificates live about 90 days and
the proxy renews them automatically with the same DNS token, so a revoked or
rotated token shows up as an expired certificate weeks later.
A name resolves but the page won't load
This is almost always DNS rebind protection. Add an exception for the name in your router or Pi-hole, or add a local DNS entry mapping the name straight to the box's LAN IP.
Why it happens, and which modes it affects
Many routers and Pi-hole setups refuse to answer a public name (like
cobblr.example.com) that points at a private LAN IP, as a security measure.
The laptop on one network loads it, and the phone on another doesn't. This only
affects the internet-facing modes. Tailscale doesn't use public DNS at all and
sidesteps it.
The public name reachable, Tailscale not (or the reverse)
These are different routes, and a device has to be on the right one.
- Tailscale. The device must be signed into the same tailnet, with the
Tailscale app running. Confirm the box is serving: on the own-hostname
(
tsnet) mode the proxy joins as its own node, so it appears in the Tailscale admin console. On the serve mode,tailscale serve statuson the box should list the app on127.0.0.1:8088. To reach a serve setup from outside the tailnet you needtailscale funnel, notserve. - A public (Cloudflare) name. This reaches the box over the public internet, which
requires the port forward on your router (TCP 443) to actually be in place and
the name to point at your current public IP. Because they put the instance on
the internet, set
PUBLIC_SIGNUP_ENABLED=falseand rely on login (see Accounts and access).
Locked out: signup is closed and nobody can get in
Because you own the box, this is always recoverable with one .env edit. See the
recovery steps in Operating and the
account model in
Accounts and access.