Use case · Team chat

Self-hosted Mattermost on a dedicated server: sizing, PostgreSQL, apt or Docker, and the first admin

Self-hosted Mattermost needs one 64-bit x86 Linux machine, PostgreSQL 15 or newer and an NGINX proxy in front of it; Mattermost's own requirements put up to 1,000 registered users on 1 core and 2 GB of RAM, and 1,000 to 2,000 on 2 cores and 4 GB. This page sizes that box, installs it from the apt repository or Docker Compose, and closes the gap most installs leave open: the first account to sign up becomes system admin, so you claim it over an SSH tunnel before port 443 answers anyone.

See the machines

Sizing figures, and which users they count

Mattermost publishes two sets of sizing figures, and they count users differently. The software and hardware requirements page sizes a single server by registered users:

Registered users (team deployment)CPURAM
1 – 1,0001 vCPU/core2 GB
1,000 – 2,0002 vCPUs/cores4 GB

Those figures assume the default 50 MB maximum file size, and the docs say peak file sharing can drive memory, so raising that limit changes the sum. Above 2,000 users they recommend Mattermost's open-source load-test framework, run against your own usage, instead of a table.

The reference architectures count concurrent users, which the docs note is commonly lower than the number of accounts. The smallest is designed for up to 200 concurrent users and lists separate machines for the application, the database and the proxy: a 2 vCPU, 4 GiB application node beside a 2 vCPU, 16 GiB database writer. From the 2,000-user design on, the pages are marked for Enterprise plans and high availability is required. Do not read those per-node figures as a single-server size; the table above is the one for one box.

The requirements name 64-bit x86 processors. Our ARM lines are the wrong pick for Mattermost.

Disk: uploads, not messages

The reference architectures give a method rather than a figure: start from roughly 600 to 800 MB for the server, operating system and database, then add storage per user per month, times 12, times the mean number of users across the year, times a safety factor of 1 to 2. The per-user bands:

  • Low, 1–5 MB per user per month: mostly text and links.
  • Medium, 5–25 MB: screenshots, PDFs and office documents dropped into channels.
  • High, 25–100 MB: teams uploading large media files as routine.

Our own arithmetic, not a docs figure: 300 users at the top of the medium band with a 2× factor need about 180 GB of new space a year. Mattermost advises checking storage use at least quarterly.

Choosing the machine

At this scale the choice is less about cores than about keeping PostgreSQL, the Mattermost process, NGINX and the uploads directory on one box with room to grow. Every line in the dedicated server range is a whole machine with root and no other tenant, so the database never shares disk or memory with a stranger's workload.

  • Moonshot cartridge: one service per box, which is what this is. The x86 cartridges with NVMe storage and at least 16 GB leave plenty over the 2 GB the docs give a 1,000-user team. Only the m510 and m710x cartridges carry a remote console; the rest have power control only.
  • Single-socket rack server: the class the range describes as an application server with its database on the same box, with an out-of-band console (HPE iLO, Dell iDRAC or a Supermicro BMC) for the day a firewall rule locks you out.
  • Dual-socket machine: when Mattermost shares the hardware with other services, or the head count is heading past the second band.

Put Ubuntu 24.04 on it through the control panel; Linux images carry no charge (see Linux dedicated servers). Its stock PostgreSQL 16 meets the minimum, while Ubuntu 22.04 ships PostgreSQL 14, which v12 refuses, unless you install PostgreSQL from the repository Mattermost's setup script adds. Mattermost v12.0 needs glibc 2.34, so Ubuntu 20.04 stops at v11.11. A racked machine is handed over in about 30 minutes once payment is approved, and the port is unmetered: file-heavy channels add no transfer line to the invoice.

Firewall, repositories and database first

  1. Turn the firewall on with SSH only: sudo ufw allow OpenSSH followed by sudo ufw enable. Check that /etc/default/ufw reads IPV6=yes, so the same rules cover the IPv6 address if you added one at checkout.
  2. Add the repositories with curl -o- https://deb.packages.mattermost.com/repo-setup.sh | sudo bash -s mattermost, then sudo apt update. Per the docs the script adds the Mattermost APT repository and a PostgreSQL package repository, and sets up NGINX and certbot; read it before you pipe it into a root shell.
  3. Install PostgreSQL with sudo apt install postgresql and confirm psql --version reports 15 or later. Mattermost v12.0, released 2026-10-16, raised the minimum to 15; the docs also allow an external or managed server.
  4. In sudo -u postgres psql, create the database as the docs give it: CREATE DATABASE mattermost WITH ENCODING 'UTF8' LC_COLLATE='en_US.UTF-8' LC_CTYPE='en_US.UTF-8' TEMPLATE=template0;. The docs then run CREATE USER mmuser WITH PASSWORD 'mmuser-password';. Our variation: CREATE USER mmuser; followed by \password mmuser, so the password is typed at a prompt instead of landing in psql's history file with the statement.
  5. Grant access: GRANT ALL PRIVILEGES ON DATABASE mattermost to mmuser;, ALTER DATABASE mattermost OWNER TO mmuser;, \c mattermost, ALTER SCHEMA public OWNER TO mmuser; and GRANT USAGE, CREATE ON SCHEMA public TO mmuser;. PostgreSQL 15 no longer lets every user create objects in the public schema, which is why the docs hand that schema to mmuser.
  6. Leave listen_addresses alone. The docs say a database local to the application needs no remote-access changes.

Installing the Mattermost package

The apt route is the one Mattermost documents for Ubuntu and Debian. With the repository already added, install the package and copy the default configuration with the owner and mode the docs give:

sudo apt install mattermost -y
sudo install -C -m 600 -o mattermost -g mattermost \
  /opt/mattermost/config/config.defaults.json \
  /opt/mattermost/config/config.json

In /opt/mattermost/config/config.json, set SqlSettings.DriverName to postgres, SqlSettings.DataSource to postgres://mmuser:PASSWORD@127.0.0.1:5432/mattermost?sslmode=disable&connect_timeout=10, ServiceSettings.SiteURL to your https:// address, and ServiceSettings.ListenAddress to 127.0.0.1:8065. By default Mattermost accepts connections on 8065 from every machine on the network; the loopback address leaves it reachable from the box alone. The file holds the database password, which is why the install line sets mode 600.

With PostgreSQL on the same host, the docs add After=postgresql.service and BindsTo=postgresql.service to the [Unit] section of /lib/systemd/system/mattermost.service directly. Our variation puts the same two lines in a drop-in made with sudo systemctl edit mattermost, which survives package upgrades. Then sudo systemctl start mattermost, check curl http://localhost:8065, and sudo systemctl enable mattermost.service.

Claiming system admin through a tunnel

Mattermost's roles documentation states that the first user added to a newly installed system gets the system admin role. Whoever reaches the sign-up page first owns the workspace, so create that account while 8065 is on loopback and nothing answers on 80 or 443.

# on your workstation, through your own login on the server
ssh -N -L 8065:127.0.0.1:8065 you@your-server
# browse to http://localhost:8065 and create the first account

Use your own SSH user, never the mattermost service account. Once signed in, confirm the Site URL in the System Console and close the tunnel.

NGINX, TLS and the websocket

NGINX is the proxy Mattermost recommends for every production deployment, handling TLS termination, the HTTP-to-HTTPS redirect and request logs. Three details from its sample configuration matter more than the cipher list:

  • Point the upstream at 127.0.0.1:8065. The docs warn that localhost can resolve to more than one address, IPv4 and IPv6.
  • Give the websocket path its own location with the Upgrade and Connection headers, as in the excerpt below.
  • client_max_body_size caps uploads at the proxy; match it to the maximum file size you set in Mattermost.
upstream backend {
   server 127.0.0.1:8065;
   keepalive 32;
}

# inside the server block listening on 443
location ~ /api/v[0-9]+/(users/)?websocket$ {
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    client_max_body_size 50M;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_http_version 1.1;
    proxy_pass http://backend;
}

Keep the docs' location / block beside this one; the excerpt shows only the websocket part.

Only after the admin exists: sudo ufw allow 'Nginx Full', issue the certificate with certbot, and sudo nginx -t before reloading. The network page says to expose 80 and 443 at the proxy and keep 8065, 8067 (metrics) and 8074 (cluster) internal. Calls, if you turn it on, needs ports of its own, listed in the Calls deployment guide.

The Docker Compose route, and ufw

The container guide clones Mattermost's docker repository, copies env.example to .env, sets DOMAIN, creates the ./volumes/app/mattermost directories, runs sudo chown -R 2000:2000 ./volumes/app/mattermost, and starts a PostgreSQL container plus a Mattermost container with Docker Compose 1.28 or later. Pin MATTERMOST_IMAGE_TAG to a full version number, as env.example in Mattermost's docker repository does (11.7.0 at the time of writing); the docs call generic release-x tags development-only.

  • The same page says this method is not for production, and does not support a cluster or HA out of the box, while the requirements page still recommends Docker on Linux. Our reading: Compose is fine for one node, and a cluster is out of scope on either route.
  • Docker publishes ports through its own iptables rules, which ufw does not filter, and a plain publish listens on IPv6 ([::]) as well as IPv4. In the without-NGINX compose file the app port comes from APP_PORT in .env: set it to 127.0.0.1:8065, put the host NGINX above in front, and claim the admin through the same tunnel. That file also publishes the Calls port on every address, so run sudo ss -tulpn and test from outside against both the IPv4 and the IPv6 address.
  • Change POSTGRES_PASSWORD in .env, as the docs recommend, and chmod 600 .env.

Running it, and where one machine stops

  • Backups: the database (pg_dump), the whole config directory and the data directory. The docs say to stop Mattermost for a clean backup so database and files stay in step, and to keep copies off-site.
  • Upgrades: sudo systemctl stop mattermost, sudo apt update && sudo apt upgrade, then start it. Stop first, because apt replaces the binary in place. The PostgreSQL minimum follows the community's end-of-life dates at each ESR, so plan database upgrades with it.
  • Search: PostgreSQL search misses email addresses and mishandles dashed terms, and only the first 1 MB of a file's content is searchable. The scaling guide requires Elasticsearch or OpenSearch above 5 million posts.
  • High availability: one machine is one failure domain, and the clustered designs are Enterprise features. When the database outgrows sharing the box, give it a PostgreSQL machine of its own.
  • Another tool may fit: Mattermost is channel-based team chat on a server you run. For federation between servers, see Matrix with Synapse; for a public, searchable community, self-hosted Discourse.

Questions

How much memory does a 500-person Mattermost team need?

Mattermost's requirements put 1 to 1,000 registered users on 1 core and 2 GB of RAM. That table is for a single server, counted by registered users. Leave headroom for PostgreSQL, NGINX and the operating system on the same box, and more if you raise the 50 MB file size limit.

Can it run on MySQL or MariaDB instead of PostgreSQL?

Not for a new install. The preparation guide says new deployments must use PostgreSQL, now version 15 or later, and MySQL support is deprecated from v11. MariaDB is not supported at all.

Does Mattermost run on ARM servers?

The requirements and scaling pages name 64-bit x86 processors. Choose one of our x86 lines rather than the Ampere Altra or X-Gene ARM machines.

What if somebody else signs up before I do?

The first account on a fresh install is given the system admin role, so that person would control the workspace. Treat the install as lost: stop Mattermost, drop and recreate the database, and start again with 8065 on loopback and the account created through an SSH tunnel.

Which ports should face the internet?

SSH, plus 80 and 443 on NGINX. Mattermost's network page says to keep 8065, the metrics port 8067 and the cluster port 8074 internal when a proxy is in front. Calls adds ports of its own if you enable it.

Do I need Docker to self-host it?

No. The signed apt repository installs Mattermost on Ubuntu 22.04 or 24.04 and Debian 12 or later (on 22.04, take PostgreSQL from the repository it adds) with a systemd unit. Docker Compose is the other documented route; if you use it, bind the published port to 127.0.0.1, because ufw does not filter ports Docker publishes.

One machine for the whole workspace

Pick an x86 line, install Ubuntu from the panel, and keep PostgreSQL, uploads and NGINX on hardware no one else uses.

See the machines

Related on Server Room

Other use cases