> ## Documentation Index
> Fetch the complete documentation index at: https://docs.postiz.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Self-Host Gotchas

> Common problems specific to running Postiz yourself

<Note>
  Problems that affect everyone (OAuth connect errors, upload limits, login
  issues) are in the [general troubleshooting section](/general/troubleshooting/overview).
  This page is only the problems you can hit because you run Postiz yourself.
</Note>

## Connect failures

### "Invalid state" caused by split domains

Postiz stores the OAuth `state` token against your session cookie. If
`FRONTEND_URL` and `NEXT_PUBLIC_BACKEND_URL` resolve to different parent
domains (for example `app.example.com` and `api.example.com`), the browser
treats the backend cookie as third-party and the token does not round-trip,
so every connect ends in "Invalid state".

**Fix:** serve both from the same parent domain, ideally through a single
reverse proxy on one hostname. See [Reverse proxies](/self-host/reverse-proxies/caddy).

### "Failed to fetch" when connecting a provider

The backend could not reach the provider's API at the network level.

**Common causes**

* A self-hosted Mastodon or WordPress instance your backend cannot reach
  (firewall, missing DNS).
* The Google Business metrics endpoint blocked by egress filtering.
* A reverse proxy in front of Postiz rewriting the outbound request.
* The logs show `Error: Blocked IP`: the URL resolved to a private IP and
  Postiz's SSRF guard rejected it. For trusted private networks, see
  [`DISABLE_SSRF_PROTECTION`](/self-host/configuration/reference#disable_ssrf_protection).

**Fix**

1. Confirm the backend container can resolve and reach the provider
   hostname: `curl https://provider.example.com/` from inside the container.
2. Behind a corporate egress proxy, set `HTTPS_PROXY` and `HTTP_PROXY` for
   the backend process.

## Everyone is logged out after a restart

`JWT_SECRET` signs session tokens. Regenerating it invalidates every
existing session.

**Fix:** set `JWT_SECRET` once during initial deployment and do not rotate
it lightly. Store it in a secret manager (Docker or Kubernetes secret,
AWS/GCP Secret Manager, Doppler, 1Password) or an untracked `.env` file on
the host. **Do not commit it to version control**, including "private"
deploy repos, since anyone with repo access then holds the signing key for
every session token Postiz has ever issued.

## "Origin not allowed" on the calendar

The backend's CORS allowlist is built from `FRONTEND_URL`, plus `MAIN_URL`
if set. Accessing Postiz on a hostname or port outside that list makes the
backend reject the call.

**Fix:** make `FRONTEND_URL` exactly match the URL you use in the browser,
protocol and port included.

## Login endpoints return 404 when logged in

If you are logged in and still get 404 from `GET /api/health` or
`GET /auth/me`, `NEXT_PUBLIC_BACKEND_URL` does not match the URL your
browser uses to reach the backend. Mismatched protocols or ports break the
session cookie.

## `pnpm` heap out of memory during install

The Postiz monorepo is large enough that the default Node heap can
overflow during `pnpm install` on small VMs.

**Fix**

```bash theme={null}
NODE_OPTIONS="--max-old-space-size=4096" pnpm install
```

If 4 GB still isn't enough and your host has the RAM to spare, raise it
to `--max-old-space-size=8192`. Don't set the heap higher than your VM's
available memory, that just trades OOM in Node for OOM-killed by the
kernel. If you're stuck on a 2 GB VM, build the image elsewhere and
deploy the prebuilt artifact instead. See
[System requirements](/self-host/installation/system-requirements) for minimum RAM.

## `postgres ECONNREFUSED`

Backend can't reach Postgres.

**Checklist**

1. `DATABASE_URL` host: is it `localhost` when the backend is in a
   container that doesn't share host networking? Use the service name
   (`postgres`) or the container IP instead.
2. Postgres ready? `docker compose logs postgres` should show
   `database system is ready to accept connections`.
3. Port mapping: if you exposed Postgres only on the Docker network,
   external `psql` won't work but the backend will. That's fine.

## "Hide a provider from the UI"

There is no env-var-based way to hide a provider from the frontend
today. The only mechanism is to set the per-organisation `disabled`
field on the Integration row in the database after a channel is
connected. We're tracking the request, see the GitHub issues tagged
`enhancement`.

## Default ports

It depends on how you're running Postiz.

**Official Docker image** (`ghcr.io/gitroomhq/postiz-app`): the
container bundles frontend + backend and exposes a single port `5000`.
The official compose maps that to host `4007:5000`, so you reach Postiz
at `http://localhost:4007/`.

**Running from source** (`pnpm dev` / `pnpm start`): frontend listens
on `4200`, backend on `3000` (overridable via `PORT`). Connect a reverse
proxy in front to give your users a single URL, see
[Reverse Proxies](/self-host/reverse-proxies/caddy).

Temporal listens on `7233` (gRPC) and the Temporal UI on `8080` in
either mode.

## Mounting the uploads volume

If `STORAGE_PROVIDER=local`, point `UPLOAD_DIRECTORY` at the path Postiz
should write to, mount that path into your container, and also set
`NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY` so the frontend knows where the
files are served from. The Next.js config rewrites `/uploads/:path*` to
`/api/uploads/:path*` when local storage is active. See
[Uploads & storage](/self-host/configuration/uploads).

For multi-container deployments, either share the same volume across all
containers that need to read or write, or move to [Cloudflare R2](/self-host/configuration/r2).

## Can I deploy Postiz on Vercel?

**Frontend:** yes, it's a standard Next.js app and deploys cleanly to
Vercel.

**Backend:** no. The Postiz backend is a NestJS server that also embeds
a Temporal worker. It needs a long-running Node host (Fly.io, Render,
Railway, a VM, a Kubernetes pod, etc.), serverless functions won't
work.

## Email isn't sending

By default, Postiz uses Resend (`EMAIL_PROVIDER=resend`) and only
becomes active when `RESEND_API_KEY` is set. To use SMTP instead, set
`EMAIL_PROVIDER=nodemailer` and provide `EMAIL_HOST`, `EMAIL_PORT`,
`EMAIL_USER`, `EMAIL_PASS`, `EMAIL_SECURE`. See
[Email configuration](/self-host/configuration/emails).

If no email provider is configured, user activation emails won't go
out, but Postiz auto-activates users in that mode, so signup still
works.

## Temporal isn't reachable

Since v2.12.0, Postiz schedules posts through Temporal. The backend
needs to reach the Temporal frontend on `TEMPORAL_ADDRESS`. If you're
upgrading from v1.x, follow the [migration guide](/self-host/installation/migration)
it walks through running both stacks side by side while you copy
Postgres data over.
