On this page
- Node, Python or another tool rejects the certificate
- The browser shows a certificate error
- A 502 page says the service is not responding
- A 404 page says the name is not configured
- Vite says the host is not allowed
- Hot reload does not connect
- A .lokal name does not resolve
- A project on .local isn’t served
- The background service is not running
- The background service runs a different build
- Port 443 or 80 is already in use
- Another program says “address already in use”
- A program listens on every address
- Setup refuses to run with sudo
- Setup asks for your password twice
- Setup fails with exit code 4
- lokal doctor fails tld.lokal
- On Linux
- Setup stops before it changes anything
- apt or dnf refuses the package
- Browsers don’t trust lokal on Linux
- .lokal names don’t resolve on Linux
- The background service on Linux
- Starting over
- Reporting a bug
Run lokal doctor first. It checks every layer and prints a fix for anything that fails: setup, the CA and its
trust, the background service and its build, the listeners, a full self-test, the .lokal resolver, and DNS, TLS
and the upstream for every service. Then look at the log:
$ lokal doctor
$ lokal logs -n 200 # or -f to follow; the file is ~/Library/Logs/lokal/daemon.logOn Linux, the log is ~/.local/state/lokal/daemon.log.
If nothing below helps, lokal setup is the repair path. It fixes whatever is missing, and asks for your password
only if a privileged step is missing.
This page describes macOS first. Problems that are specific to Linux are under On Linux.
Node, Python or another tool rejects the certificate#
Errors look like unable to get local issuer certificate, self-signed certificate in certificate chain or
CERTIFICATE_VERIFY_FAILED. Many tools ship their own CA bundle and ignore the macOS keychain (or, on Linux, the
system certificate store). Run this in the shell that starts them:
$ eval "$(lokal env)"This must happen before the process starts. Node reads NODE_EXTRA_CA_CERTS only at startup. Add the line to
your shell profile to make it permanent. Recent Node versions can also use the keychain directly with
node --use-system-ca.
Trusting lokal from Node, Python and curl explains each variable.
The browser shows a certificate error#
- Run
lokal doctor. Ifca.trustedfails, runlokal setup. It re-trusts the CA and asks for your password. - Firefox has its own trust store. Setup turns on Firefox’s
security.enterprise_roots.enabledpreference in each profile. If you created a Firefox profile after setup, runlokal setupagain. - On Linux, Chrome and Firefox use their own certificate databases. See Browsers don’t trust lokal on Linux.
- If only one name fails, with a handshake error rather than a certificate warning (curl:
tlsv1 alert access denied), that name probably isn’t configured. Checklokal list: lokal presents certificates only for configured names, and for names under a wildcard service ('*').
A 502 page says the service is not responding#
The page says something like api.myapp.lokal → localhost:4000 is not responding. Is the service running?.
lokal is working, but nothing answered on the upstream port within 2 seconds.
- Make sure your dev server is running, on the port in
lokal list. - If
lokal upstarted it,lokal logs <sub> -n 50shows its recent output: whether it crashed, exited (lokal: exited 1) or is still starting.lokal restart <sub>starts it again under thatlokal up, and returns once the URL works. localhostupstreams work whether the server listens on127.0.0.1or::1. If it listens on another address, set it:lokal service update <sub> --host <address>.lokal doctorshowsupstream.<name>aswarnwhen nothing is listening.
A 404 page says the name is not configured#
The page says <name> is not configured. The name resolves but has no route. (Over HTTPS, an unconfigured name
fails the TLS handshake instead.) The page lists the project’s URLs. Add the service, or check for a typo in the
name.
Vite says the host is not allowed#
Vite shows “Blocked request. This host is not allowed” when it gets a Host header it doesn’t recognise. Allow
your lokal names in vite.config.js (server: { allowedHosts: ['.myapp.lokal'] }), or switch the service to
send the upstream’s own host with lokal service update <sub> --host-header upstream.
Other dev servers that check the Host header work the same way. See
Using lokal with your framework.
Hot reload does not connect#
lokal proxies WebSockets, so hot reload works through it. If your dev server’s client connects to a hard-coded
URL such as ws://localhost:5173 instead of the page’s own origin, configure it to use the lokal URL (the page’s
host, port 443, wss://). Check your dev server’s documentation for its WebSocket URL setting.
A .lokal name does not resolve#
lokal doctorchecksresolver.lokal. If it fails,/etc/resolver/lokalis missing or wrong, so runlokal setup.scutil --dnsshould list a resolver for domainlokalwith nameserver127.0.0.1, port 53535.- The background service must be running (
lokal status). It answers the DNS queries. - If
/etc/hostshas an entry for the same name, it can win.lokal doctorwarns about this (dns.conflict.<name>). - Some VPN and DNS-filtering apps take over all DNS resolution, so
.lokalnames don’t resolve while one is active. lokal has no way around that: it serves only.lokalnames, and they go through DNS. - On Linux,
.lokallookups go through systemd-resolved instead. See.lokalnames don’t resolve on Linux.
A project on .local isn’t served#
Since 0.6.0, lokal serves only .lokal names. A project created on .local by an older version stays in your
state, but it gets no names, certificates or routes, so its URLs don’t work. lokal status, lokal list,
lokal project list and lokal doctor (its project.<name> check) say which project that is, and
lokal service add, lokal service update, lokal run and lokal up refuse it. lokal never moves a project for
you. To move one:
$ lokal project remove <name> --force
$ lokal project create <name>
$ lokal service add <sub> <port> --project <name>Add each service again the same way. If the project comes from a lokal.toml, run lokal up in its repo instead
of create and service add. A domain = "….local" line in lokal.toml fails with the .lokal name to use.
.lokal names has the details.
The background service is not running#
Commands say the background service isn't running; start it with 'lokal start'. Start it and check:
$ lokal start
$ lokal statusIf it won’t stay up, read lokal logs -n 200. You can also check launchd’s view with
launchctl print gui/$UID/dev.lokal.daemon, and launchd’s own output in ~/Library/Logs/lokal/launchd.err.log.
On Linux, systemd runs it instead: see The background service on Linux.
The background service runs a different build#
Commands warn the background service runs a different lokal build; run 'lokal upgrade'. You updated the lokal
CLI, but the background service still runs its old copy. Switch it over:
$ lokal upgradeRun this after every brew upgrade lokal, or after you replace the binary by hand. make install does it for
you. See Upgrade and uninstall.
Port 443 or 80 is already in use#
Another program (another local proxy, a web server, Docker publishing port 443) held one of lokal’s addresses
when lokal’s background service started. lokal serves on the addresses it got, and says which one it couldn’t
open, what that costs, and the fix. You see it in lokal setup, lokal start, lokal restart, lokal upgrade,
the listeners check of lokal doctor, and as missing: in lokal status. lokal restart, for example, prints:
warning: 127.0.0.1:443 is taken by nginx (pid 812), so lokal serves HTTPS only on [::1]:443
hint: stop nginx (pid 812) or move it to another port, then run 'lokal restart'Requests to that address reach the other program, not lokal, so some tools get a different certificate or page.
If port 443 or lokal’s DNS port is taken, lokal setup and lokal doctor fail. Port 80 alone is a warning: HTTPS
works, and only the http:// to https:// redirect is missing there.
When the other program runs as another user, such as a web server started as root, lokal can’t see its name. The message then ends with the command that names it:
$ sudo lsof -nP -iTCP:443 -sTCP:LISTENOn Linux, it’s sudo ss -ltnp 'sport = :443'.
Stop that program or move it to another port, then run lokal restart. It opens the addresses it couldn’t open
before. If the program let go of the port already, the message says the address is free now, and lokal restart
is all you need.
Another program says “address already in use”#
If lokal started first, a program that tries to listen on port 443 or 80 fails, because lokal holds the port on
127.0.0.1 and ::1. Give that program another port. If it’s a dev server, lokal can give it an HTTPS name
instead: see Projects and services.
A program listens on every address#
A program that listens on port 443 or 80 on every address (*:443, as Docker does when it publishes a port) can
run next to lokal. lokal’s addresses are more specific, so https://localhost and http://localhost reach lokal,
not that program. lokal doctor warns about it. If you use that program on this computer, give it another port.
Setup refuses to run with sudo#
Setup says run 'lokal setup' as yourself, not with sudo. Don’t prefix it with sudo. lokal asks for your
password itself, for the steps that need it.
Setup asks for your password twice#
That’s expected on macOS. The first prompt is sudo in Terminal. The second is a macOS dialog, because macOS confirms every change to certificate trust settings separately. On Linux, setup asks once.
Setup fails with exit code 4#
Setup needed sudo but couldn’t ask for a password. Either no terminal is attached (for example, it ran from a
script or a coding agent), or LOKAL_FORBID_SUDO=1 is set. Run lokal setup yourself in a Terminal window.
lokal doctor fails tld.lokal#
.lokal has become a real top-level domain in the public DNS, and lokal hides real .lokal websites on this
computer. lokal serves only .lokal names, so there’s no other TLD to move to. lokal uninstall removes the
resolver config that sends .lokal lookups to lokal. Please
report it. The
security model explains the risk.
On Linux#
Linux is a preview. Setting up lokal on Linux describes what setup changes there.
Setup stops before it changes anything#
Setup checks that each step can work on this machine first. If one can’t, it stops with
lokal can't be set up here yet: and the reason, and nothing is changed:
systemd-resolved isn't running. lokal sends.lokallookups through systemd-resolved, version 258 or later. Ubuntu and Fedora run it by default.this systemd-resolved has no DNS delegates. systemd is older than 258, such as on Ubuntu 24.04 (255) or Debian 13 (257). Setup can’t run there yet. Ubuntu 26.04 and Fedora 43 have systemd 258 or later.programs here don't ask systemd-resolved for names. The hosts line of/etc/nsswitch.confhas noresolve, and/etc/resolv.confdoesn’t point at resolved’s stub, so programs would never see lokal’s names. On Debian and Ubuntu,sudo apt install libnss-resolveaddsresolveto the hosts line. Under WSL, which writes its ownresolv.conf, you need it.no system certificate store: install the ca-certificates package. Installca-certificates.
Then run lokal setup again.
apt or dnf refuses the package#
The .deb and .rpm require systemd 258 or later, which setup needs, so apt and dnf refuse them where setup
couldn’t run. apt’s message names systemd (>= 258). Ubuntu 24.04 has systemd 255, and Debian 13 has 257. Use
Ubuntu 26.04, Fedora 43 or later. Linux (preview) has the details.
Browsers don’t trust lokal on Linux#
Chrome, Chromium and Firefox on Linux don’t read the system certificate store. Setup adds lokal’s CA to their own
databases with certutil, and lokal doctor checks them in its firefox check:
certutil is missing, so browsers don't trust lokal yet. Installlibnss3-tools(Debian, Ubuntu) ornss-tools(Fedora), then runlokal setupagain.Chrome has no certificate database yet. Chrome is installed but hasn’t run yet. Runlokal setupagain, which creates the database.<database> don't trust lokal's CA; run 'lokal setup'. A browser profile was created after setup, such as a new Firefox profile. Runlokal setupagain.
.lokal names don’t resolve on Linux#
lokal doctor checks the delegate in resolver.lokal:
.lokal lookups aren't sent to lokal./etc/systemd/dns-delegate.d/lokal.dns-delegateis missing. Runlokal setup.… exists but systemd-resolved hasn't loaded it. resolved reads delegates when it starts. Runsudo systemctl restart systemd-resolved, orlokal setup.programs here don't ask systemd-resolved for names. See Setup stops before it changes anything.
To look yourself, compare resolved’s answer with the one other programs get:
$ resolvectl query api.myapp.lokal # asks systemd-resolved directly
$ getent ahosts api.myapp.lokal # looks the name up the way other programs doBoth should give 127.0.0.1 and ::1. If resolvectl does and getent doesn’t, programs on this machine don’t
ask resolved: install libnss-resolve. If neither does, run lokal doctor. It checks the delegate and the
background service, which answers the DNS queries.
The background service on Linux#
The background service is a set of systemd user units in ~/.config/systemd/user/: dev.lokal.daemon.service
and its three socket units, dev.lokal.daemon-https.socket, dev.lokal.daemon-http.socket and
dev.lokal.daemon-dns.socket. To see systemd’s view:
$ systemctl --user status dev.lokal.daemon.service dev.lokal.daemon-https.socketlokal logs shows the service’s log, ~/.local/state/lokal/daemon.log. Its stdout and stderr are in the same
folder, in launchd.out.log and launchd.err.log.
If the HTTPS and HTTP socket units fail with Permission denied, user services may no longer bind ports below
1024. sysctl net.ipv4.ip_unprivileged_port_start should print 80 or lower. lokal setup sets it again.
If one of them fails with Address already in use, another program holds that port. The service runs on the
other sockets, and lokal names the program: see Port 443 or 80 is already in use.
Starting over#
$ lokal uninstall --purge # removes everything, including your projects and the CA
$ lokal setupSetup then creates a new CA, so it asks for your password again.
Reporting a bug#
Please open an issue and include:
lokal --versionandsw_vers(on Linux,cat /etc/os-release);lokal doctor --json;- the relevant part of
lokal logs -n 200; - what you ran, what you expected, and what happened.
lokal doctor --json and the logs contain your project and service names. Remove anything you don’t want to
share.
To report a security vulnerability, don’t open a public issue. Report it privately through GitHub security advisories instead.