The panel shows every visitor, page and address on your site, so it is never served without a password. With no credentials configured it answers 503 and refuses to serve — an analytics dashboard left open on the internet is a data breach, and defaults decide outcomes.
| Mode | What you get | What it costs |
|---|---|---|
| The browser’s own prompt | HTTP Basic. Every tool that speaks HTTP signs in the same way, so curl --user and monitoring checks work. |
The prompt cannot be styled, and there is no way to sign out short of closing the browser. |
| A sign-in page | Loghound’s own form, a real session with an idle timeout and an absolute one, and a Sign out button that destroys the session on the server. Required for two-factor and for staying signed in. | It only works in a browser. Scripts cannot sign in to it, so move any monitoring check to Basic or drop it. |
Chosen during setup, changeable afterwards in Settings without setting the password again. Changing it applies to the next request.
Session mode keeps its session files in the directory the FPM pool names. If you switch to it on a host where it has never been used, create that directory and give it to the service user first — the permission model has it at mode 0700, because a session file is a signed-in session.
Timeouts
Session mode only, and both enforced by the application on every request rather than left to PHP’s session garbage collector, which is best-effort by design and runs on somebody else’s request:
- Idle timeout — thirty minutes by default. Ends a session that has done nothing for that long.
- Absolute timeout — twelve hours. Ends one that long after sign-in, however busy.
An expired session is destroyed on the spot and you land back on the sign-in page, so a timeout is a door that reopens rather than a dead end.
Eight failed sign-ins from one address inside fifteen minutes lock that address out until the window passes. It applies to both modes, to the two-factor step, and to a request arriving with a stay-signed-in cookie — a stolen cookie is not a way round it.
A global counter on a login form is a denial-of-service primitive handed to anyone who can reach it: a few thousand wrong passwords from a botnet would trip it and lock the legitimate operator — whose password is correct and whose address has zero failures — out of their own panel for as long as the attacker cared to keep going. Per address, an attacker can only lock out the address they are attacking from. The cost is that a distributed guesser is not slowed by the lockout at all, only by a randomised delay and by whatever entropy is in the password. Put the panel behind an address allowlist or a VPN if that matters to you.
The counter is a file with addresses stored hashed. Nothing clears it faster than the window except deleting that file; restarting PHP-FPM does nothing to it. If the ledger cannot be written, sign-in is refused rather than allowed — a check that cannot be performed is a failed check.
In session mode the form also refuses to say which half was wrong, so it cannot be used to enumerate usernames, and a failed Basic attempt sleeps a randomised fraction of a second to blunt online guessing.
The sign-in form offers it, and it is off by default. A browser that takes it is signed in with no idle timeout and no maximum session age: the session survives closing the browser, restarting the machine, and any amount of inactivity, and ends only when somebody presses Sign out.
Whoever holds that browser profile has this panel, indefinitely. There is no timeout to save you from a lost laptop. Leave the box unticked on a shared or portable machine.
It is not just a long-lived cookie, because a long-lived cookie does not work: PHP deletes session files on a schedule of its own, and a cookie that outlives the file it names signs you out by accident — the exact thing the option exists to prevent. The cookie carries a lookup id and a secret, of which only a hash of the secret is stored.
Figure 1 — why the secret rotates and the lookup id does not. That asymmetry is exactly what makes a replayed secret detectable rather than indistinguishable from a stale cookie. “Once” has a width of thirty seconds, because several tabs revalidating on wake legitimately present one cookie at the same moment — and an alarm that fires every morning is not an alarm.
Six things revoke it: signing out (which revokes every token, not just this browser’s), signing in with the box unticked, the control in Settings that signs every remembered browser out, changing the password, changing the sign-in method, and turning two-factor on — because a token issued on the strength of one factor cannot survive as a way past two.
Standard six-digit authenticator codes. Off by default, and it needs the sign-in page mode, because the browser’s own prompt has no second step to put a code in. Set it up in Settings, not by hand:
- Press the setup button. It mints a fresh secret and shows a QR code — drawn on your own server, in pure PHP. No chart-server URL, no third-party encoder: sending the shared secret to somebody else to have a picture of it drawn would hand them your second factor.
- Scan it with any standard authenticator app, or type the key in by hand — it is shown grouped in fours for exactly that.
- Enter the code the app shows now. Nothing is stored until this is accepted, so closing the tab half way through leaves two-factor off and locks nobody out.
- Save the ten recovery codes. Shown once, downloadable, and only their hashes are stored. Each works once. They are the only way back in if you lose the phone, and you can issue a fresh set at any time, which invalidates the old one.
An unconfirmed enrollment expires fifteen minutes after you press the button, on the clock and not on the session — because staying signed in means a session can now last forever, and a pending secret must not inherit that. A code is accepted within one step either side of now for clock drift, and once: the last accepted step is recorded and anything at or below it is refused, so a code read over your shoulder is worth thirty seconds of nothing.
The sign-in second step, the enrollment confirmation, turning two-factor off, and reissuing recovery codes. A recovery code counts the same as a six-digit one, and a wrong code costs exactly what a wrong password costs — so no endpoint is the cheaper place to guess, and alternating between them buys no extra attempts. Six digits is a million possibilities, and that limiter is the only thing between a stolen password and a guessed code.
Turning two-factor off requires a current code or a recovery code, and so does reissuing recovery codes — never just being signed in. If a stolen session were enough to strip the second factor, the second factor would be protecting nothing; and ten fresh recovery codes are a standing way past the phone, so minting them is the same act by a quieter route.
Lost the phone and the recovery codes? Run the setup wizard on the server and set a new password. That turns two-factor off and revokes every remembered browser. Being able to run it is already proof of who you are — which is also why there is no reset by email: Loghound has no mail path and would not use one for this.