A small forum you host yourself, with nothing going to anyone else. Free and open source.
LibreForum is for communities that want to talk to each other without handing anything to a third party: no tracking, no ads, no outside scripts, fonts or avatars. It is one PHP app and one MariaDB or MySQL database.
Status: early development. Everything below works end to end and is covered by tests, but expect changes before the first release.
- Categories, threads and replies, in plain text. Web addresses become links, and
codeand code blocks keep their spacing. - Members only. People join with a one-time invitation link and choose a username. No email address is asked for or stored.
- Announcements: a category can be set so that only moderators start threads in it, while everyone can reply.
- Moderation: pin, lock, move and delete threads, mute a member (they can still read), and a Report link under every post that feeds a queue for moderators.
- "New since your last visit" markers, so you can see what changed in a thread.
- Members can delete their own posts. A member who leaves is shown as "Former member" and their posts stay.
- Limits on how fast members can post. Moderators are never limited.
- A clean default look that follows the device's light or dark setting, and themes you can swap or adjust with a few color and font settings.
- No third parties. No outside fonts, scripts, avatars or analytics. Every page is sent with a policy that stops the browser loading anything from anywhere else.
- No IP addresses stored. Limits on wrong passwords use an anonymous code that changes every day.
- Only the cookie it needs to keep you logged in. Only a scrambled copy of it is kept on the server.
- No email addresses. Nothing is sent by mail, and members can't see anything about each other except a username.
- Not for search engines. Every page says "noindex", and links to other sites don't tell them where the visitor came from.
- Deleted really means deleted. Deleted threads and posts are hidden at once and removed from the database after 30 days.
PHP 8.2 or newer and MySQL or MariaDB. No other services.
-
Put the whole folder on your server and make
public/the website's root. Everything else (code, settings, tools) must stay outside it. The included.htaccessis for Apache. For nginx:root /path/to/libreforum/public; location / { try_files $uri /index.php?$query_string; } location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php-fpm.sock; }
It also works in a sub-folder of a site (
example.com/forum/). -
Create a MariaDB/MySQL database and user, copy
config.example.phptoconfig.php, and fill in the database details and the forum's name. -
Create the tables:
php bin/install.php -
Open the forum's address in a browser. The first visitor names the forum and creates the owner's login. (Or do it from the command line:
php bin/owner.php yourname "Forum name". Do one of the two right after installing, before anyone else finds the address.) -
Add a cron job that runs once a day:
php bin/maintain.php --quiet -
Use HTTPS, and turn
display_errorsoff in PHP's settings on a live server.
Log in as the owner, open Manage → Invitations, and make a link for each person you want to invite. The link works once and is shown only once. When someone opens it they see the house rules, choose a username and a password, and they are in.
To try it on your own computer without a web server, run php -S 127.0.0.1:8080 -t public bin/router.php in this folder and open http://127.0.0.1:8080.
Want to see it with some conversations in it first? php bin/demo.php fills an empty forum with made-up people and threads (owner demo_owner, everyone's password demo-password-123). Don't run it on a forum people really use.
- Owner: everything. Invites people, makes moderators, removes members, and edits the categories, the forum's name and the house rules.
- Moderator: pins, locks, moves and deletes, mutes members, and works through reports. Can start threads in announcement categories.
- Member: starts threads, replies, reports posts, deletes their own posts. A member can delete their own thread as long as nobody else has replied to it.
| Command | What it does |
|---|---|
php bin/install.php |
Creates the tables. Safe to run again. |
php bin/owner.php name "Forum name" [--host-ref=ID] |
Creates the owner, instead of the setup page. With --host-ref the owner has no password (see "Running it inside another app"). |
php bin/invite.php [member|moderator] [days] |
Prints an invitation link. |
php bin/category.php list | add | rename | staff-only | up | down | delete |
Manages categories. |
php bin/member.php list | mute | unmute | moderator | member | remove | password | host-only |
Manages people. password sets a new password for someone who lost theirs. host-only name ID makes someone come in only through the app that runs the forum (their password is erased). |
php bin/maintain.php [--quiet] |
Daily housekeeping. |
php bin/demo.php |
Fills an empty forum with pretend conversations. |
php -S 127.0.0.1:8080 -t public bin/router.php |
Runs the forum on your own computer with PHP's built-in web server, for trying it out. |
config.example.php lists every setting with a comment. The ones you are most likely to change:
name: the forum's name until the owner sets one in the browser.url: the forum's full address, used when the command-line tools print links.limits: posts per hour, new threads per day and seconds between posts for ordinary members.reserved_names: usernames nobody can pick (the forum's own name is always reserved).trusted_proxies: if the forum sits behind a reverse proxy or CDN, list its addresses so the visitor's real address is used for the wrong-password limit.themeandtheme_paths: see below.
Settings can also be given from a different file with the LIBREFORUM_CONFIG environment variable, which is handy in containers.
A control panel, a customer area or any app that already logs its own people in can run the forum for them, so nobody needs a second password.
-
Put a long random secret in
config.phpand give the same secret to the host app:'host' => [ 'secret' => 'a long random text', 'frame_ancestors' => ['https://host.example'], // pages that may show the forum inside a frame (optional) 'idle_minutes' => 60, // how long such a login lasts when unused 'max_hours' => 12, // and in total ],
-
Create the owner with no password:
php bin/owner.php name "Forum name" --host-ref=ID, where ID is the host app's own id for that person. -
When one of its people wants the forum, the host app sends their browser to
https://forum.example/enter?t=TOKEN, or shows that address in a frame. A token isbase64url(payload) + "." + base64url(HMAC-SHA256(payload text, secret)), and the payload is JSON:v(1),ref(the host's id for the person),acct(its id for their account),acct_name(optional),iatandexp(Unix times, at most 5 minutes apart: a minute is plenty),jti(32 random hex characters) andnext(an optional first page such as/t/12). Each link works once.lib/host.phphaslf_host_token_make(), which a PHP host can copy.
The host app can also manage the team of one of its accounts, meaning the people who log in on the forum's own page with a password of their own. It sends signed JSON to POST /host/api: {"op": "team.list", "acct": "42"} lists the team, open invitations and places used; team.create (username and password, both chosen by the account holder) adds somebody, who can log in at once; team.password (member, password) sets a new password for somebody on the team and ends their logins, which is how a forgotten password gets fixed, since the forum keeps no email addresses; team.invite (with a note saying who it is for, and optional days) makes a one-time link instead, shown only in that answer; team.revoke (invite) cancels one; team.remove (member) takes somebody off the team. The three headers are X-Host-Time (Unix time, within 2 minutes), X-Host-Nonce (32 random hex characters, used once) and X-Host-Signature, the hex HMAC-SHA256 of time.nonce.body with the same secret. Each account gets host.team_limit places (5 unless set, 0 for no limit), counting its account holder. A host can only see and change the account named in the request.
A host can also pause an account, for example when its customer stops paying, and later resume it, or remove it for good. acct.suspend means nobody from the account can come in through the host, log in on the forum's own page or accept an invitation, and their open logins stop working at their next click; what they wrote stays exactly as it was. acct.resume lets them back (a login that hasn't run out works again). acct.remove turns everybody in the account into a "Former member" (what they wrote stays), ends their logins, cancels its open invitations and leaves the account paused; the answer says how many people were removed. None of them touches the forum's own staff: an account that has the owner or a moderator can't be paused or removed by the host. An account nobody has come from yet has nothing to pause, so asking is simply fine. While an account is paused, team.create and team.invite are refused.
People who come in this way have no password on the forum and can't use its own login page at all. Their login ends when the browser closes, after idle_minutes without use, or after max_hours. The first time, they choose a username on the welcome screen. People with the same acct belong to one forum account, so a member limit can be set for it. Other people (a team, say) can still get in the ordinary way with an invitation link and a password. When the owner removes someone, the host can't bring them back.
LibreForum ships with a clean default theme. Every color, font and size is a token at the top of themes/default/assets/theme.css, so you can make it match your own site without touching the code.
- Small changes: make a folder
themes/mine/assets/with acustom.cssthat overrides the tokens, and set'theme' => 'mine'inconfig.php. - Bigger changes: copy any page from
themes/default/templates/into your theme and edit it. Anything a theme doesn't have comes from the default theme. - A theme can bring its own default house rules: put them, one per line, in
templates/default-rules.txt. They stand until the owner writes rules in the browser. - Themes can live outside the code folder: put
'theme_paths' => ['/path/to/my/themes']inconfig.php.
The tests need a separate MariaDB/MySQL user and two throwaway databases (they are wiped on every run). tests/config.example.php says how to set them up.
php tests/run.php # the library, against a database
php tests/e2e.php # the whole forum over HTTP, plus the command-line tools
Copyright (C) 2026 RJC3rd.
GNU Affero General Public License v3.0. You're free to use, study, change, and share LibreForum. If you share it, or run a changed version for other people over a network, you must keep it under the same license and share your source too, so it stays free for everyone. The footer's "Source code" link is there for that: if you run a changed copy, point source_url in config.php at your own source.