# How to self-host a ChatGPT alternative

Canonical: https://www.swfte.com/how-to-self-host-chatgpt-alternative
Last verified: 2026-10-06
Difficulty: Intermediate
Time: About 1 to 2 hours for a working pilot, plus time for single sign-on and a short policy for users.
Cost: Free software. You pay for the server, power and the time to maintain it. Hosted model APIs, if you connect any, are billed by their providers.
Hardware: The interface itself is light. The model is what needs memory: a small model on a modern laptop or workstation for a pilot, a GPU server for a shared team.

## Short answer

To self-host a ChatGPT alternative, run Open WebUI in Docker on a private address, point it at a model you host (Ollama for small teams, vLLM for shared GPU servers), create the admin account first, then switch new sign-ups to approval. Put HTTPS in front, back up the data volume and pin the image version. Privacy depends on the model backend, not the interface.

## Who this is for

- IT leads and engineers setting up a private chat interface for a team, a department or a pilot.
- Anyone who wants a ChatGPT-style experience where prompts stay on infrastructure they control.
- Teams who already have Ollama or a vLLM endpoint and need a front end for non-technical colleagues.

Not for:
- Teams who need governed agents, permission-aware company search and an audit trail from day one. The interface covers only part of that; see the limits section below.
- Anyone who wants to white-label the interface for customers. Read the Open WebUI licence section first.

## Prerequisites

- A Linux server or workstation with Docker installed. The commands below use Linux.
- A model backend you control. This guide uses Ollama; if you have a vLLM server, [how to self-host an LLM](https://www.swfte.com/how-to-self-host-an-llm) sets one up.
- A hostname for the interface if more than one person will use it, and the ability to open a firewall port.
- An email-style admin identity you can keep: the first account created becomes the administrator.

## The Open WebUI licence: read it if you will rebrand

Open WebUI is free to run, but its licence is not a plain open-source one. From v0.6.6 (19 April 2025) it adds a branding clause: you may not alter, remove or obscure the "Open WebUI" branding unless you have 50 or fewer users in a 30-day period, or have permission or an enterprise licence. The project's own page says that licence is not OSI-approved. Versions up to v0.6.5 stayed under BSD-3-Clause.

For an internal tool that keeps the branding, this changes little. If you plan to rebrand for a larger group or for customers, read the licence text and ask the project. This is orientation, not legal advice.

## Steps

### Step 1: Decide where prompts will go

Outcome: A written choice of model backend, because it decides your privacy, not the chat interface.

Open WebUI is an interface. The model behind it generates the text, and wherever that model runs is where your prompts are sent. A self-hosted interface pointed at a hosted API still sends every prompt to that provider. Sometimes that is what you want. Decide on purpose.

For a pilot, Ollama on the same machine is the shortest path: prompts never leave the box. For a shared team, a vLLM server on a GPU machine handles many users better. For tasks the data class allows, you can add a hosted model later through a gateway. Write the choice into your acceptable-use note so users know which data may go where.

| Backend | Where prompts go | Good for | Watch out for |
| --- | --- | --- | --- |
| Ollama on the same host | Stay on the host | Pilots, small teams, laptops and workstations | Limited concurrency; one person's long request can slow others |
| vLLM on a GPU server you run | Stay on your network | Shared teams with real concurrency | You now operate a GPU server |
| A hosted API | Go to that provider | Hard tasks that your data policy allows | Anyone with a key can send data out; control keys centrally |

### Step 2: Install Ollama and pull a model

Outcome: A model answers from the command line and over Ollama's local API.

On Linux, the Ollama documentation gives a one-line installer, and the service runs under systemd. Pull a model, run it once to see that it works, then test the API. The Ollama quickstart uses `gemma4:e2b` as its example, which is a small model suited to a first test. Pick a larger one later once you know how much memory you have; our [guide to running LLMs locally](https://www.swfte.com/how-to-run-llms-locally) covers sizing.

By default Ollama listens on 127.0.0.1 port 11434. That is the right default; you will change it in the next step only if you need to.

Install Ollama (Linux):

```bash
curl -fsSL https://ollama.com/install.sh | sh
```

Check the service:

```bash
sudo systemctl status ollama
```

Download and try a small model (type /bye to leave):

```bash
ollama pull gemma4:e2b
ollama run gemma4:e2b
```

Test the REST API:

```bash
curl http://localhost:11434/api/chat \
  -H "Content-Type: application/json" \
  -d '{"model": "gemma4:e2b", "messages": [{"role": "user", "content": "Say hello in one sentence."}], "stream": false}'
```

### Step 3: Let the Open WebUI container reach Ollama

Outcome: Ollama is reachable from Docker, and still not reachable from the wider network.

Open WebUI will run in a container, and the container cannot see 127.0.0.1 on the host. The Open WebUI troubleshooting page gives three fixes for the common "server connection error": run the container with `--network=host`, set `OLLAMA_HOST=0.0.0.0` so Ollama accepts connections beyond localhost, or use `host.docker.internal` as the hostname. The quick-start command already adds `--add-host=host.docker.internal:host-gateway` for that third route.

The Ollama FAQ shows how to set the variable for a systemd service: edit the service with `systemctl edit ollama.service`, add the environment line, then reload and restart. Be careful with what this does. Ollama has no login. Listening on 0.0.0.0 makes it answer anyone who can reach port 11434, so block that port at the firewall for everything except the Docker network.

If you would rather avoid this step entirely, Open WebUI publishes an image with Ollama bundled in (the `:ollama` tag). It is simpler, but it ties the model and the interface together in one container, which is harder to scale and upgrade separately.

Open the service override and add the lines below:

```bash
sudo systemctl edit ollama.service
```

Add this under [Service] in the editor:

```text
[Service]
Environment="OLLAMA_HOST=0.0.0.0:11434"
```

Apply it:

```bash
sudo systemctl daemon-reload
sudo systemctl restart ollama
```

> WARNING: Do not publish port 11434 to the internet. Anyone who can reach it can use your model and read what it serves.

### Step 4: Start Open WebUI

Outcome: Open WebUI runs on localhost:3000 with its data in a named volume and a persistent secret key.

Generate a secret key first. The Open WebUI documentation says the key must persist: without it, every time the container is recreated everyone is logged out. Then run the container.

This is the default command from the Open WebUI quick start with three changes. The port binds to 127.0.0.1 so nothing is exposed until your proxy is ready. `OLLAMA_BASE_URL` points at the host through `host.docker.internal`. And you should replace `:main` with a specific version tag for anything beyond a trial; the docs say `:main` is a rolling tag, and the update page warns it "can include breaking changes without warning". Look up the current release tag on the project's releases page and use that.

The `open-webui` volume holds your chats, users and settings. The documentation puts it bluntly: never run without it.

Create a secret key:

```bash
export WEBUI_SECRET_KEY="$(openssl rand -hex 32)"
echo "$WEBUI_SECRET_KEY"   # store this in your password manager
```

Run Open WebUI:

```bash
docker run -d -p 127.0.0.1:3000:8080 \
  --add-host=host.docker.internal:host-gateway \
  -v open-webui:/app/backend/data \
  -e WEBUI_SECRET_KEY="$WEBUI_SECRET_KEY" \
  -e OLLAMA_BASE_URL=http://host.docker.internal:11434 \
  --name open-webui --restart always \
  ghcr.io/open-webui/open-webui:main
```

With an NVIDIA GPU, the quick start uses the :cuda image and --gpus all:

```bash
docker run -d -p 127.0.0.1:3000:8080 --gpus all \
  --add-host=host.docker.internal:host-gateway \
  -v open-webui:/app/backend/data \
  -e WEBUI_SECRET_KEY="$WEBUI_SECRET_KEY" \
  -e OLLAMA_BASE_URL=http://host.docker.internal:11434 \
  --name open-webui --restart always \
  ghcr.io/open-webui/open-webui:cuda
```

### Step 5: Create the admin account, then lock down sign-up

Outcome: You are the administrator, and new people cannot get in without approval.

Browse to http://localhost:3000 and sign up. The Open WebUI roles documentation says the very first account on a fresh installation is automatically given the Admin role. That makes the first sign-up an important moment: do it yourself, straight after the container starts, before anyone else knows the address.

Later sign-ups get the Pending role by default, which has no access until an admin approves it. The environment reference lists `DEFAULT_USER_ROLE` with options pending, user and admin, and a default of pending, and `ENABLE_SIGNUP` for toggling account creation. One quirk catches people out. Settings marked as persistent config apply from the environment only on the first launch; after that the value stored in the database wins. So to change sign-up behaviour later, use the admin settings in the interface, or set `ENABLE_PERSISTENT_CONFIG` to `False` and accept that changes made in the UI will then not be saved.

If you will use single sign-on, plan it now. The reference documents `ENABLE_OAUTH_SIGNUP` (default False) and notes that `OPENID_PROVIDER_URL` should be set for the Google and Microsoft sign-in options, otherwise logout may not work. Follow the provider-specific section in the environment reference for the rest of the variables.

> TIP: Keep two admin accounts so that losing one login does not lock you out. Use real named accounts, not a shared one, so that admin actions can be attributed.

### Step 6: Connect the model backends you chose

Outcome: The model picker lists your models, and only the backends you approved.

Ollama models appear on their own once `OLLAMA_BASE_URL` is right. For anything else, the Open WebUI documentation says adding a provider is "as simple as entering a URL and API key" in Settings, Admin, Connections. For an OpenAI-compatible server such as vLLM, the base URL ends in `/v1`.

You can also set these by environment variable. `OPENAI_API_BASE_URL` defaults to `https://api.openai.com/v1`, and `OPENAI_API_KEY` holds the key. Take that default seriously. If someone puts a hosted key in there, prompts go to that provider, so decide who may add connections, and keep provider keys in one place under an owner. A gateway in front of all model traffic is the clean answer once you have more than one backend; see [how to set up an LLM gateway](https://www.swfte.com/how-to-set-up-an-llm-gateway).

Test with a sentence that contains nothing sensitive, then check on the model server that the request arrived there. That is how you prove where prompts actually go.

Example: connect a vLLM server on the same host by environment variable:

```bash
docker rm -f open-webui
docker run -d -p 127.0.0.1:3000:8080 \
  --add-host=host.docker.internal:host-gateway \
  -v open-webui:/app/backend/data \
  -e WEBUI_SECRET_KEY="$WEBUI_SECRET_KEY" \
  -e OPENAI_API_BASE_URL=http://host.docker.internal:8000/v1 \
  -e OPENAI_API_KEY="$VLLM_KEY" \
  --name open-webui --restart always \
  ghcr.io/open-webui/open-webui:main
```

Check the Ollama OpenAI-compatible endpoint if you prefer to connect to Ollama that way:

```bash
curl http://localhost:11434/v1/models
```

> NOTE: The data volume survives removing and recreating the container, so users and chats persist. Because of persistent config, settings you already saved in the admin interface can override environment values on restart; check Admin Settings after any change.

### Step 7: Set roles, groups and the privacy defaults

Outcome: People have the access they need and no more, and admins cannot casually read chats.

Open WebUI separates roles (Admin, User, Pending) from groups and permissions. Roles set the baseline trust level. Groups organise users and grant additional permissions or shared access to resources, and permissions are granular feature flags such as whether a user can delete chats or use web search. The model is additive: users start with their default rights and group membership adds capabilities. So keep the default role minimal and grant more through groups.

Admin access to other people's conversations is not automatic. The documentation says that the `ENABLE_ADMIN_CHAT_ACCESS=False` variable can stop admins viewing user chats. Decide your position on this before you tell staff the tool is private, and tell them what you chose. If your policy needs administrators to review conversations, say so in the acceptable-use note instead of leaving people to assume.

Finally, write a short note for users that says which data classes may be entered, which models are connected and where they run, how long chats are kept, and who to ask for help. A one-page note does more for privacy than most settings.

### Step 8: Put HTTPS in front with a reverse proxy

Outcome: Users reach the interface at https://chat.example.com, and the container port is not exposed.

Browsers and password managers behave properly only over HTTPS, and sign-in tokens should not cross a network in clear text. Caddy keeps this short: it provisions certificates automatically for a public domain name. The Caddy quick start shows a site address followed by a `reverse_proxy` line, then `caddy run` from the same directory. Caddy's documentation also states that its proxy supports WebSocket connections, which chat interfaces use for streaming.

If the server has no public DNS name, automatic certificates will not work. Use an internal certificate authority or your organisation's existing proxy and certificate process instead. Whatever you use, open the firewall for the proxy port only, and leave port 3000 bound to 127.0.0.1 as in step 4.

Caddyfile (replace the hostname):

```text
chat.example.com {
	reverse_proxy localhost:3000
}
```

Start Caddy from the directory with the Caddyfile:

```bash
caddy run
```

### Step 9: Back up the data and plan upgrades

Outcome: A tested backup and a known routine for updating the interface.

Back up before every upgrade. The Open WebUI update page warns that database migrations are one-way: rolling back to an older version will not undo them, so the only safe rollback is to restore from a backup taken before the update. It gives a backup command that archives the volume with a throwaway container.

The update itself is: remove the container, pull the image, run the same command again. Your data persists in the volume. Pin the version tag so that "update" means a version you chose, tested on a copy first if the interface matters to many people.

Test the restore once. A backup that has never been restored is a guess.

Back up the data volume:

```bash
docker run --rm -v open-webui:/data -v "$(pwd)":/backup \
  alpine tar czf /backup/openwebui-$(date +%Y%m%d).tar.gz /data
```

Update: remove, pull, recreate (use the same flags as step 4):

```bash
docker rm -f open-webui
docker pull ghcr.io/open-webui/open-webui:main
```

## Alternatives to Open WebUI

LibreChat is the main alternative. Its repository states an MIT licence and lists multi-user authentication with OAuth2, LDAP and email login, Model Context Protocol tool support and agents. The documented Docker route is `git clone https://github.com/LibreChat-AI/LibreChat.git`, `cd LibreChat`, `cp .env.example .env`, then `docker compose up -d`, with the app on http://localhost:3080, and `docker compose pull` followed by `docker compose up` to update. Choose it if you want one front end across many providers under a permissive licence.

Our [comparison of Open WebUI, LibreChat and AnythingLLM](https://www.swfte.com/blog/self-hosted-chatgpt-alternative-open-source-compared) covers the differences in more depth. This guide sticks to Open WebUI because it is the closest match to the ChatGPT experience with a local model engine.

## What this setup does not give you

The interface is only part of the work. After this guide you have private chat. You do not yet have permission-aware search over company documents, because built-in document chat indexes what you give it and respecting source permissions is your integration work. You do not have an audit record, since chat history is not evidence. You do not have governed agents, which need scoped tool access and approval steps. And you have a service to patch, back up and support.

If those are requirements, plan them early. See [how to build a company brain for AI](https://www.swfte.com/how-to-build-a-company-brain-for-ai) for the retrieval side and [how to govern AI agents](https://www.swfte.com/how-to-govern-ai-agents) for the control side.

## Troubleshooting

| Symptom | Likely cause | Fix |
| --- | --- | --- |
| Open WebUI shows "Server Connection Error" or no Ollama models | The container cannot reach Ollama on the host, usually because Ollama listens only on 127.0.0.1. | Set `OLLAMA_HOST=0.0.0.0:11434` on the Ollama service (step 3), keep `--add-host=host.docker.internal:host-gateway`, and use `http://host.docker.internal:11434` as the base URL. The troubleshooting page also offers `--network=host`; then use `OLLAMA_BASE_URL=http://127.0.0.1:11434`. |
| Everyone is logged out each time the container is recreated | No persistent `WEBUI_SECRET_KEY` was set. | Set the same `WEBUI_SECRET_KEY` on every run, as in step 4, and store it in a password manager. |
| You changed `ENABLE_SIGNUP` or another setting in the command and nothing changed | Persistent config: after the first launch, the stored database value takes precedence over the environment. | Change it in the admin settings, or set `ENABLE_PERSISTENT_CONFIG` to `False` if you want environment variables to always win. |
| New colleagues sign up but see nothing | New accounts get the Pending role until an admin approves them (the default). | Approve the account in the admin user list, or set a different default role if your policy allows it. |
| The model list is empty after you added a vLLM connection | The URL is missing the `/v1` suffix, the key is wrong, or the container cannot reach the host port. | Use `http://host.docker.internal:8000/v1`, send the key you set with `--api-key`, and test the same URL from inside the container network. |
| Chats are lost after an update | The container ran without the data volume mounted. | Always mount `-v open-webui:/app/backend/data`. Restore from your backup if you have one, and note that migrations cannot be rolled back. |
| The page loads but streaming responses stall behind a proxy | The proxy is buffering responses or not passing WebSocket upgrades. | The Caddy documentation says its proxy supports WebSocket connections. With another proxy, allow upgrade requests and disable response buffering for the chat path. |

## Verify it worked

- [ ] http://localhost:3000 loads on the server, and port 3000 is not reachable from another machine.
- [ ] The first account you created is an Admin, and a test sign-up from another browser lands in Pending.
- [ ] The model picker shows only the backends you approved, and a test prompt appears in the log of the model server you expected.
- [ ] https://chat.example.com loads with a valid certificate and a streamed answer arrives token by token.
- [ ] Port 11434 (Ollama) is not reachable from outside the host or Docker network.
- [ ] A backup file exists, and you have restored it once on a scratch container.
- [ ] Your acceptable-use note says which data may be entered and where each model runs.

## Next steps

- [How to self-host an LLM](https://www.swfte.com/how-to-self-host-an-llm): a shared GPU endpoint for the interface to use
- [How to set up an LLM gateway](https://www.swfte.com/how-to-set-up-an-llm-gateway): central keys, budgets and logs for every model behind the chat
- [How to detect shadow AI](https://www.swfte.com/how-to-detect-shadow-ai): find out which other tools your colleagues already use
- [AI workspace versus ChatGPT Enterprise](https://www.swfte.com/blog/ai-workspace-vs-chatgpt-enterprise-2026): when a hosted assistant is the better answer

## FAQ

### What is the best self-hosted ChatGPT alternative?

There is no single best. Open WebUI is closest to the ChatGPT experience with local models. LibreChat is strongest for many providers under an MIT licence. Pilot two against your own requirements and users before you commit.

### Is Open WebUI really open source?

Its code is public and free to run, but from v0.6.6 it carries a branding clause, and the project's own licence page says it is not an OSI-approved open-source licence. For internal use that keeps the branding the practical effect is small.

### Is a self-hosted ChatGPT alternative private by default?

The interface runs on your server, but privacy depends on the model backend. With a model you host, prompts stay on your infrastructure. If you connect a hosted API, prompts go to that provider.

### Can I use Open WebUI with Ollama and vLLM?

Yes. Open WebUI connects to Ollama through `OLLAMA_BASE_URL` and to any OpenAI-compatible server, including vLLM, through a connection URL ending in `/v1` and an API key.

### Do I need a GPU to run a private ChatGPT?

Not for a pilot with a small model on a modern workstation, and not at all if you point the interface at an API. A shared team with larger models needs a GPU server. The interface itself needs little.

### Who is the administrator in Open WebUI?

The first account created on a fresh installation is automatically the Admin. Later sign-ups are Pending until approved, by default. Create your admin account yourself immediately after the first start.

## How Swfte can help

This guide works without Swfte. If you want the layers around the chat window, Swfte has a governed desktop client and a model gateway.

- [Cortex](https://www.swfte.com/products/cortex): a governed AI desktop that runs local by default and answers from company files
- [Swfte Connect](https://www.swfte.com/products/connect): the gateway that controls provider access and logs model traffic
- [Company brain platform page](https://www.swfte.com/platform/company-brain): the knowledge layer behind a company-wide assistant

The company brain is partly in progress: asking questions across company documents is not yet generally available. Check the platform page for current status.

## Sources

- [Open WebUI Docker quick start](https://docs.openwebui.com/getting-started/quick-start/): docker run commands, :main, :cuda and :ollama images, data volume, WEBUI_SECRET_KEY advice, version pinning advice
- [Open WebUI environment variable reference](https://docs.openwebui.com/reference/env-configuration): OLLAMA_BASE_URL, OPENAI_API_BASE_URL, OPENAI_API_KEY, WEBUI_SECRET_KEY, ENABLE_SIGNUP, DEFAULT_USER_ROLE, ENABLE_OAUTH_SIGNUP, OPENID_PROVIDER_URL, persistent config behaviour
- [Open WebUI: connect a provider](https://docs.openwebui.com/getting-started/quick-start/connect-a-provider/): adding providers in Settings, Admin, Connections
- [Open WebUI roles](https://docs.openwebui.com/features/authentication-access/rbac/roles.md): first account is Admin, Pending role, ENABLE_ADMIN_CHAT_ACCESS
- [Open WebUI RBAC overview](https://docs.openwebui.com/features/authentication-access/rbac/): roles, groups, permissions, additive model
- [Open WebUI connection error troubleshooting](https://docs.openwebui.com/troubleshooting/connection-error): --network=host, OLLAMA_HOST=0.0.0.0, host.docker.internal, base URL fixes
- [Open WebUI updating guide](https://docs.openwebui.com/getting-started/updating/): backup command, one-way migrations, pinned versions, docker update commands
- [Open WebUI licence page](https://docs.openwebui.com/license/): branding clause, 50 user threshold, not OSI-approved, v0.6.6 date
- [Ollama Linux install](https://docs.ollama.com/linux): install script, systemctl status ollama
- [Ollama quickstart](https://docs.ollama.com/quickstart): ollama pull and run with gemma4:e2b, /api/chat curl example
- [Ollama FAQ](https://docs.ollama.com/faq): default bind 127.0.0.1:11434, OLLAMA_HOST, systemd environment override steps
- [Ollama OpenAI compatibility](https://docs.ollama.com/api/openai-compatibility): base URL http://localhost:11434/v1/, /v1/models
- [Docker port publishing](https://docs.docker.com/engine/network/port-publishing/): 127.0.0.1 port binding and default all-address publishing
- [Caddy reverse proxy quick start](https://caddyserver.com/docs/quick-starts/reverse-proxy): Caddyfile with a site address and reverse_proxy, caddy run
- [Caddy reverse_proxy directive](https://caddyserver.com/docs/caddyfile/directives/reverse_proxy): WebSocket connections supported
- [LibreChat Docker setup](https://www.librechat.ai/docs/local/docker): git clone, .env copy, docker compose up -d, port 3080, update commands
- [LibreChat repository](https://github.com/danny-avila/LibreChat): MIT licence, OAuth2 LDAP email login, MCP support, agents

Last verified against these sources on 2026-10-06.
