NextChat Self-Hosted LLM Chat UI on AWS User Guide
Overview
This image runs NextChat 2.16.1 (ChatGPTNextWeb/NextChat, MIT) on Ubuntu 24.04 LTS. NextChat is an open source, self hosted web interface for chatting with large language models: it gives a team one fast, private chat front end that talks to the model providers you already pay for, instead of routing conversations through somebody else's hosted product.
NextChat runs as the official upstream container under Docker, pinned by image digest so the deployment is reproducible and never silently upgrades. The container publishes on port 80. The default security group opens port 22 (SSH) and port 80 (HTTP) only.

This image contains no model and no inference
NextChat is a front end, not a model host. This image ships no model weights, no inference engine, no API key and no model credits. You bring your own model provider account. Every prompt you send is delivered to the provider you configure, and that provider bills you directly for token usage.
NextChat supports OpenAI and Azure OpenAI, Anthropic, Google Gemini, DeepSeek, Moonshot, Alibaba Qwen, ByteDance, iFlytek, xAI, ChatGLM, SiliconFlow and any OpenAI compatible endpoint, so one deployment can front several providers at once.
Because NextChat is CPU only with no model to load, it runs comfortably on a small instance and needs no GPU.
The access code gate ships ENABLED
This is the load-bearing security control on this image, so it is worth understanding.
NextChat gates server side provider access on a CODE value, but its own logic fails open: it derives "is a code required?" from how many codes are configured, so an empty CODE silently switches the gate off. An instance in that state, with a working provider key set, would let anyone who finds the address spend your provider credits.
cloudimg closes that in two places:
- On the first boot of every deployed instance, a one-shot service generates a fresh 32 character access code, unique to that instance, and writes it to
/root/nextchat-credentials.txt(mode0600, root only). It is never a fixed or shared value and it is never baked into the image. nextchat.servicecarries anExecStartPrepreflight guard that refuses to start the service at all if the access code is missing, empty, shorter than 24 characters, or a well known placeholder.
So there is no default login to change, and no window in which the instance runs ungated.

Where your data goes
Worth being precise about, because "self hosted" does not mean "nothing leaves the instance":
- Stays on your instance: the NextChat application, its configuration, and any provider key you set on the server.
- Stays in the browser: conversation history. NextChat keeps chat history in browser storage, not in a server side database, so there is no chat database on the instance to back up.
- Leaves your instance: the prompts themselves. They go to whichever model provider you configure, over that provider's API, under that provider's terms. Self hosting the interface does not make the prompts private from your provider.
Storage layout
One dedicated EBS volume keeps the container image store off the operating system disk, independently resizable:
| Mount point | Contents |
|---|---|
/var/lib/docker |
Docker data-root: the digest-pinned NextChat image |
/var/lib/containerd |
Bind mounted to /var/lib/docker/containerd-store. Docker 29 keeps image layers in the containerd store, so this must live on the same dedicated volume or the layers land back on the operating system disk |
Both are recorded in /etc/fstab (the data volume by filesystem UUID), so the layout reproduces on every instance you launch.
There is no application data volume, because NextChat holds chat history in the browser and keeps no server side database.
Connecting to your instance
| OS variant | SSH login user | Connect with |
|---|---|---|
| Ubuntu 24.04 LTS | ubuntu |
ssh -i /path/to/key.pem ubuntu@<public-ip> |
| What you need | Where to get it |
|---|---|
| The per-instance access code | sudo cat /root/nextchat-credentials.txt on the instance |
| The web interface | http://<public-ip>/ in a browser |
| Provider configuration file | sudo nano /var/lib/nextchat/.env, then sudo systemctl restart nextchat.service |
The credentials file is mode 0600 and owned by root, so it is readable only with sudo.
Launching the instance
From the AWS Marketplace console, choose the listing, then Continue to Subscribe → Continue to Configuration → Continue to Launch. Pick your instance type (m5.large is the recommended size), your key pair, and a security group.
Restrict inbound port 80 to your own networks. The chat interface is served over plain HTTP on port 80. The access code gate protects your provider credits, but it is not a substitute for network controls or TLS — see Putting TLS in front below.
To launch from the AWS CLI instead, substitute your own AMI id, key pair, subnet and security group:
| Step | Command |
|---|---|
| Launch | aws ec2 run-instances --image-id <ami-id> --instance-type m5.large --key-name <key-name> --security-group-ids <sg-id> --subnet-id <subnet-id> |
| Find the address | aws ec2 describe-instances --instance-ids <instance-id> --query 'Reservations[0].Instances[0].PublicIpAddress' --output text |
First run
First boot takes a minute or so while the access code is generated and the container starts. Check that both services are up:
systemctl is-active docker.service nextchat.service
Confirm the container is running the digest-pinned image and publishing on port 80:
cd /var/lib/nextchat && sudo docker compose ps
The image is pinned by digest, so you can verify exactly which upstream release you are running from the image's own labels:
sudo docker image inspect yidadaa/chatgpt-next-web@sha256:eaaa469ddeeb5fa58fb35f8767e9e096a2f1c8468c54b6703323624bf3071c5a --format 'version={{index .Config.Labels "org.opencontainers.image.version"}} licence={{index .Config.Labels "org.opencontainers.image.licenses"}} revision={{index .Config.Labels "org.opencontainers.image.revision"}}'
The revision is the upstream git commit that release tag points at, and the licence label is the upstream project's own declaration.
Confirm the access code gate is on
/api/config is what the browser reads to decide whether to ask for an access code. needCode must be true:
curl -s http://127.0.0.1/api/config | jq .
Prove it for yourself: a chat request with no credentials is refused by the gate, with a specific error, before it ever reaches a provider.
curl -s -o /tmp/nc-check.json -w 'HTTP %{http_code}\n' --max-time 30 -X POST -H 'Content-Type: application/json' -d '{"model":"gpt-4o-mini","stream":false,"messages":[{"role":"user","content":"ping"}]}' http://127.0.0.1/api/openai/v1/chat/completions; cat /tmp/nc-check.json; rm -f /tmp/nc-check.json
The reply is HTTP 401 with the body {"error":true,"msg":"empty access code"}. That exact body is the important part: an HTTP status alone is not proof, because a request that does pass the gate and reaches an unconfigured provider also comes back 401 — with the provider's own error object instead. The "msg":"empty access code" marker is what tells you the gate itself did the refusing.
Check that the fail-closed preflight guard is attached to the service:
systemctl show -p ExecStartPre --value nextchat.service
Retrieve your access code
Read the credentials file over SSH (see the Connecting to your instance table above for the exact command). It contains the instance URL and the access code. You can confirm a code was generated without printing it:
sudo awk -F= '/^NEXTCHAT_ACCESS_CODE=/{printf "access code present: %d characters\n", length($2)}' /root/nextchat-credentials.txt
Then open http://<public-ip>/ in a browser, go to Settings, and paste the code into Access Code.

Connecting your own model provider
You have two options, and they can be mixed.
Per person, in the browser. Each user opens Settings and pastes their own provider API key into the key field. Nothing is stored on the server. This is the best fit when people already hold their own provider accounts.
Once, for the whole instance. Edit /var/lib/nextchat/.env on the instance, set the key for the provider you use, and restart the service. The file is mode 0600 and owned by root.
| Step | Command |
|---|---|
| Edit the configuration | sudo nano /var/lib/nextchat/.env |
| Apply it | sudo systemctl restart nextchat.service |
You can see which settings the file exposes without revealing any value:
sudo grep -oE '^[A-Z_]+=' /var/lib/nextchat/.env | tr '\n' ' '; echo
Confirm for yourself that no provider key ships in this image. This counts the provider key entries that have a value, and on a freshly launched instance it is 0:
sudo awk -F= '/^[A-Z0-9_]*API_KEY=./{n++} END{printf "provider API keys with a value: %d\n", n+0}' /var/lib/nextchat/.env
BASE_URL points at the upstream provider endpoint by default. Set it to your own OpenAI compatible endpoint (a local gateway, a proxy, or another vendor) to route through that instead.
Server side tool execution (ENABLE_MCP) is left disabled, as upstream ships it, so the appliance does not execute tools on the instance out of the box.
Using NextChat
Conversations live in the left sidebar. Start a new one with New Chat, and switch model or provider per conversation from the chat toolbar.
Prompt templates ("masks") let you pin a reusable system prompt, a model and its parameters, and start new conversations from it. The image ships the upstream template library.

Starting a new chat offers the template picker first, or you can skip straight to a blank conversation.

The interface is a progressive web app, so it installs to a desktop or phone home screen from the browser and works well on a small screen.
Storage and operations
Confirm the container image store is on the dedicated volume rather than the operating system disk:
df -h / /var/lib/docker
Docker 29 keeps image layers in the containerd store, so check that it resolves to the same device:
findmnt -no SOURCE,TARGET /var/lib/containerd; df --output=source /var/lib/docker /var/lib/containerd | tail -2
Both must report the dedicated volume. If they ever diverge, image layers are filling your operating system disk.
To resize the volume, grow the EBS volume in the AWS console, then extend the filesystem on the instance:
| Step | Command |
|---|---|
| Grow the filesystem | sudo resize2fs $(findmnt -no SOURCE /var/lib/docker) |
Check which ports are actually served:
ss -tlnH | awk '{print $4}' | sort -u
Only 22 and 80 are exposed beyond loopback. The entries on 127.0.0.53 and 127.0.0.54 are the systemd resolver, which is loopback only.
Service logs:
| What | Command |
|---|---|
| Service unit | sudo journalctl -u nextchat.service -n 100 --no-pager |
| First boot | sudo journalctl -u nextchat-firstboot.service -n 100 --no-pager |
| Container | cd /var/lib/nextchat && sudo docker compose logs --tail 100 nextchat |
Changing the access code
Set a new value for CODE in /var/lib/nextchat/.env and restart. CODE accepts a comma separated list, so you can issue several codes and revoke one without disturbing the others.
| Step | Command |
|---|---|
| Edit | sudo nano /var/lib/nextchat/.env |
| Apply | sudo systemctl restart nextchat.service |
The preflight guard enforces a minimum of 24 characters and rejects well known placeholder values. If the service refuses to start after an edit, that guard is why — sudo journalctl -u nextchat.service -n 20 --no-pager will show the reason. Removing the access code entirely is not a supported configuration: the service will refuse to start rather than run as an open relay.
Putting TLS in front
The image serves plain HTTP on port 80. Before exposing an instance beyond a trusted network, terminate TLS in front of it. Two straightforward options:
- An AWS Application Load Balancer with an ACM certificate, forwarding to the instance on port 80, with the instance's security group accepting port 80 only from the load balancer.
- A reverse proxy on the instance (nginx or Caddy) holding the certificate and proxying to the container. The container publishes on host port 80, so move that mapping in
/var/lib/nextchat/docker-compose.ymlbefore binding a proxy to 80.
Restricting the security group to your own network ranges is worth doing in either case.
Licensing and support
NextChat is MIT licensed, so there is no per user or per seat software fee. The image ships the upstream release pinned by digest; the licence label above is the upstream project's own declaration for exactly the artifact that runs here.
The cloudimg charge covers packaging, security hardening, patching, image maintenance and support. You pay your model provider directly for token usage.
NextChat is an independent open source project. This image is produced by cloudimg and is not affiliated with or endorsed by the NextChat project.
Support
24/7 support from cloudimg by email at support@cloudimg.co.uk and via live chat at cloudimg.co.uk. We aim to respond within one business day.
Support covers deployment of this image, the access code gate, service and container health, operating system patching, and configuring the image against your own model provider. Model provider accounts, API keys and token billing remain with your chosen provider.