Deployment guide

NextChat

Overview

NextChat (formerly ChatGPT-Next-Web) 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. You can organize conversations in a sidebar, pin reusable system prompts, pick from a built-in prompt template library, switch model or provider per conversation, and export or share a transcript. It is a progressive web app, so it installs to a desktop or phone home screen and works well on a small screen.

NextChat is a front end, not a model host. It calls an LLM provider API, so it runs comfortably on a small CPU-only VM and needs no GPU. It supports OpenAI and Azure OpenAI, Anthropic, Google Gemini, DeepSeek, Moonshot, Alibaba Qwen, ByteDance, iFlytek, xAI, ChatGLM and SiliconFlow, plus any OpenAI-compatible endpoint, so one deployment can front several providers at once.

This guide covers deploying and configuring NextChat on Ubuntu 24.04 on Azure, using SecureAnyCloud Azure Marketplace images. The image ships the free, open-source, MIT-licensed NextChat as the official upstream container, pinned by image digest and captured into the VM, so your instance starts in seconds and never silently upgrades.

No provider API key is ever baked into the image. NextChat's own access check is disabled whenever no access code is configured, so a unique 32-character access code is generated for each VM on first boot, and a preflight guard refuses to start the service without one.

NextChat is a trademark of its respective owner. This image is produced by SecureAnyCloud and is not affiliated with, endorsed by, or sponsored by the NextChat project. It ships the free, open-source, MIT-licensed, self-hosted software unmodified.

What you get:

  • NextChat 2.16.1 (the MIT-licensed upstream container yidadaa/chatgpt-next-web), pinned by image digest, not by a moving tag.
  • Docker Engine and the Compose plugin from the official Docker APT repository.
  • The web UI published on port 80 (mapped to the container's port 3000). Nothing else is exposed.
  • nextchat.service and nextchat-firstboot.service as systemd units, enabled and active on boot.
  • A unique 32-character access code generated per VM on first boot, never baked into the image.
  • A fail-closed preflight guard: the service refuses to start if the access code is missing, empty or too short.
  • Every LLM provider credential shipped empty. You always supply your own.
  • MCP server-side tool execution left disabled, the upstream default.
  • Ubuntu 24.04 LTS base with the latest security patches applied at build time.
  • Azure Linux Agent for cloud integration and SSH key injection.
  • 24/7 SecureAnyCloud support with a guaranteed 24-hour response SLA.

Prerequisites

  • An active Azure subscription, an SSH public key, and a VNet and subnet in the target region.
  • A subscription to the NextChat listing on Azure Marketplace.
  • An API key for at least one LLM provider (OpenAI, Anthropic, Google Gemini, DeepSeek and others are supported). SecureAnyCloud does not supply one and never bundles one.
  • Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) is ample. NextChat is a single prebuilt Node.js server with no database and no local model, so it idles at roughly 150 MB of memory. Increase the size only for a large number of concurrent conversations.
  • NSG inbound rules: allow 22/tcp from your management network and 80/tcp for the web UI from the networks that use it.
1

Deploy from the Azure Portal

Sign in to the Azure Portal, choose Create a resource, search the Marketplace for NextChat by SecureAnyCloud, and select Create. On Basics, pick your subscription, resource group, region and size. Under Administrator account, choose SSH public key and paste your key. Under Inbound port rules, allow SSH (22) and HTTP (80). Then select Review + create and Create.

2

Deploy from the Azure CLI

RG="nextchat-prod"; LOCATION="eastus"; VM_NAME="nextchat-01" IMAGE_URN="sac1647283583153:nextchat-ubuntu-24-04:default:latest" SSH_KEY="$(cat ~/.ssh/id_rsa.pub)" az group create --name "$RG" --location "$LOCATION" az network vnet create -g "$RG" --name nc-vnet --address-prefix 10.100.0.0/16 --subnet-name nc-subnet --subnet-prefix 10.100.1.0/24 az network nsg create -g "$RG" --name nc-nsg az network nsg rule create -g "$RG" --nsg-name nc-nsg --name allow-ssh --priority 100 \
  --source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp az network nsg rule create -g "$RG" --nsg-name nc-nsg --name allow-http --priority 110 \
  --source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 80 --access Allow --protocol Tcp
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:nextchat-ubuntu-24-04:default:latest
az vm create -g "$RG" --name "$VM_NAME" --image "$IMAGE_URN" \
  --size Standard_B2s --storage-sku StandardSSD_LRS \
  --admin-username azureuser --ssh-key-values "$SSH_KEY" \
  --vnet-name nc-vnet --subnet nc-subnet --nsg nc-nsg --public-ip-sku Standard
3

Connect to your VM

ssh azureuser@<vm-ip>
4

Confirm the services are running

NextChat runs as one container under nextchat.service, published on host port 80. Confirm that the services are active and view the container:

sudo systemctl is-active docker nextchat-firstboot nextchat
sudo docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'

All three services report active, and the container is Up with only 0.0.0.0:80->3000/tcp published:

active active active NAMES STATUS PORTS nextchat Up About a minute 0.0.0.0:80->3000/tcp, [::]:80->3000/tcp
5

Read the per instance access code

First boot generated a unique 32-character access code for this VM, before the app was reachable, and wrote it to a root-only file. Read it:

sudo cat /root/nextchat-credentials.txt

The file (mode 0600 root:root) holds NEXTCHAT_URL and NEXTCHAT_ACCESS_CODE. Neither ships in the image. The code is unique to this VM, so no two instances share one and there is no default to change.

6

Understand the access code security model

This is the most important section of this guide. NextChat protects a server-side provider key with an access code. Its own rule is that the check is enabled only when at least one code is configured. An empty access code therefore silently turns the gate off, and an instance with a working provider key would let anyone who finds the address spend your credits.

This image closes that gap. The code is generated per VM, and nextchat.service runs a preflight guard that refuses to start without one.

Confirm that the gate is on and that requests without a valid code are refused:

B=http://localhost curl -s $B/api/config | jq '{needCode, hideUserApiKey, disableGPT4}' curl -s -o /dev/null -w 'no access code: HTTP %{http_code}\n' -X POST \
  -H 'Content-Type: application/json' -d '{"model":"gpt-4o-mini","messages":[]}' \
  $B/api/openai/v1/chat/completions curl -s -X POST -H 'Content-Type: application/json' \
  -d '{"model":"gpt-4o-mini","messages":[]}' $B/api/openai/v1/chat/completions | jq -c . curl -s -o /dev/null -w 'wrong access code: HTTP %{http_code}\n' -X POST \
  -H 'Content-Type: application/json' -H 'Authorization: Bearer nk-not-the-code' \
  -d '{"model":"gpt-4o-mini","messages":[]}' $B/api/openai/v1/chat/completions curl -s -X POST -H 'Content-Type: application/json' -H 'Authorization: Bearer nk-not-the-code' \
  -d '{"model":"gpt-4o-mini","messages":[]}' $B/api/openai/v1/chat/completions | jq -c .

needCode is true (the gate is enabled), and both an absent and a wrong access code are refused with 401:

{ "needCode": true, "hideUserApiKey": false, "disableGPT4": false } no access code: HTTP 401 {"error":true,"msg":"empty access code"} wrong access code: HTTP 401 {"error":true,"msg":"wrong access code"}

Send the same request again with your access code from Step 5:

CODE='<NEXTCHAT_ACCESS_CODE>' curl -s -X POST -H 'Content-Type: application/json' \
  -H "Authorization: Bearer nk-${CODE}" \
  -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"ping"}]}' \
  http://localhost/api/openai/v1/chat/completions | jq -c .

Before you set a provider key, the reply is no longer a NextChat refusal. It is an error from the provider itself, which shows that the access code was accepted and your request was forwarded:

{"error":{"message":"Incorrect API key provided: nk-...","type":"invalid_request_error","param":null,"code":"invalid_api_key"}}

An "error": true with a "msg" field means NextChat is refusing you. An "error" object with a "message" field means your provider is answering. Step 7 supplies the key that turns this into a real model response.

7

Supply your own LLM provider API key

SecureAnyCloud never ships a provider key. There are two ways to supply yours.

Option A: each person brings their own key (no server configuration). Open the web UI, go to Settings, and paste a personal API key into the provider key field. Requests then use that person's key and their own billing. Nothing needs to change on the VM.

Option B: one key for everyone on this VM. Set the key once in the environment file. Edit /var/lib/nextchat/.env with sudo nano /var/lib/nextchat/.env and fill in the provider you use:

# one of these, matching your provider OPENAI_API_KEY=<your-token> BASE_URL=https://api.openai.com
# or Anthropic ANTHROPIC_API_KEY=<your-token>
# or Google Gemini GOOGLE_API_KEY=<your-token>

Restart the service to apply the change:

sudo systemctl restart nextchat.service

With Option B, the key lives only on your VM, and the access code from Step 5 is what stops anyone else from using it. Any OpenAI-compatible endpoint works: point BASE_URL at it and set OPENAI_API_KEY to that service's key.

The environment file is the whole configuration surface. Every provider credential ships empty:

sudo grep -vE '^\s*#' /var/lib/nextchat/.env | grep -v '^$' \
  | sed -E 's|^(CODE=).*|\1<generated on this VM at first boot>|'
CODE=<generated on this VM at first boot> OPENAI_API_KEY= <set-at-first-boot>=https://api.openai.com OPENAI_ORG_ID= ANTHROPIC_API_KEY= <set-at-first-boot>= ANTHROPIC_URL= GOOGLE_API_KEY= <set-at-first-boot>= DEEPSEEK_API_KEY= <set-at-first-boot>= SILICONFLOW_URL= ENABLE_MCP= HIDE_USER_API_KEY= <set-at-first-boot>= DISABLE_GPT4= DISABLE_FAST_LINK= CUSTOM_MODELS= DEFAULT_MODEL=
8

Open the web UI and enter the access code

Browse to http://<vm-ip>/ to reach NextChat. Open Settings and paste the NEXTCHAT_ACCESS_CODE from Step 5 into Access Code. The panel confirms Access control Enabled. This is also where a user pastes their own provider key if you chose Option A in Step 7.

9

Start a conversation

Close Settings to reach the conversation workspace: the conversation list on the left, the message pane in the middle, and the composer at the bottom. Select New Chat to start a fresh conversation at any time.

Type a message and select Send. The reply streams back into the conversation and renders as formatted markdown, with the model name shown above it. Your message goes from the browser to NextChat on your VM, out through its server-side provider proxy to the model provider you configured, and the reply comes back and renders here.

10

Use the prompt template library

Choose Mask in the sidebar to open the built-in Prompt Template library. It holds reusable system prompts that pre-configure a conversation for a task, each with its own model and settings. Filter by language or search, select Chat to start a conversation from a template, or select Create to save your own.

11

Confirm the pinned release and its license

The container is pinned by image digest, not by a moving tag, so your instance ships an exact, reproducible artifact. The image's own OCI labels record the upstream release, its license and the exact source commit it was built from:

NC_IMAGE='yidadaa/chatgpt-next-web@sha256:eaaa469ddeeb5fa58fb35f8767e9e096a2f1c8468c54b6703323624bf3071c5a'
sudo docker inspect --format '{{json .Config.Labels}}' "$NC_IMAGE" | jq '{ version: ."org.opencontainers.image.version", licence: ."org.opencontainers.image.licenses", source: ."org.opencontainers.image.source", revision:."org.opencontainers.image.revision"}'
{ "version": "v2.16.1", "licence": "MIT", "source": "https://github.com/ChatGPTNextWeb/NextChat", "revision": "557a2cce357749c6fb3176d42e03ff6f7de4d355" }
12

Confirm the fail closed guard

nextchat.service runs a preflight guard before every start. If the access code is ever removed, emptied or shortened, the service refuses to start rather than come up ungated. Confirm that the guard is wired in, and see it refuse:

sudo systemctl show -p ExecStartPre --value nextchat.service | head -1 printf 'CODE=\n' | sudo tee /tmp/demo.env >/dev/null
sudo /usr/local/sbin/nextchat-preflight.sh /tmp/demo.env; echo "exit=$?"
sudo rm -f /tmp/demo.env

The unit shows the guard in its ExecStartPre, and the guard exits 1 on an empty access code:

exit=1 nextchat-preflight: REFUSING TO START — CODE is empty in /tmp/demo.env. NextChat would run with NO access-code gate, leaving any configured provider key open to the internet. Set a long random CODE and try again.
13

Rotate the access code

The access code is a shared secret for this instance. To roll it, write a new long random value and restart the service:

NEW_CODE="$(openssl rand -base64 64 | tr -dc 'A-Za-z0-9' | cut -c1-32)" sudo sed -i "s|^CODE=.*|CODE=${NEW_CODE}|" /var/lib/nextchat/.env
sudo sed -i "s|^NEXTCHAT_ACCESS_CODE=.*|NEXTCHAT_ACCESS_CODE=${NEW_CODE}|" /root/nextchat-credentials.txt
sudo systemctl restart nextchat.service
sudo systemctl is-active nextchat.service

Everyone then re-enters the new code in Settings. CODE accepts a comma-separated list, so you can also issue a different code per person and revoke one without disturbing the others.

14

Server components

The image contains these components:

  • Application: NextChat 2.16.1 (yidadaa/chatgpt-next-web), pinned by image digest.
  • Upstream source: github.com/ChatGPTNextWeb/NextChat, commit 557a2cce (git tag v2.16.1).
  • Runtime: Node.js 18 (prebuilt Next.js standalone server) inside the container.
  • Front door: container port 3000, published on host :80.
  • Orchestration: Docker Compose under nextchat.service.
  • Access code: per instance, 32 characters, generated on first boot into /var/lib/nextchat/.env and /root/nextchat-credentials.txt.
  • Fail-closed guard: /usr/local/sbin/nextchat-preflight.sh as the unit's ExecStartPre.
  • Provider credentials: none shipped. You supply them in /var/lib/nextchat/.env, or each user supplies one in the browser.
  • Server-side data: none. Conversations are stored in the browser.
  • MCP tool execution: disabled (ENABLE_MCP empty, the upstream default).
  • Operating system: Ubuntu 24.04 LTS (patched at build).
  • License: MIT (NextChat).
15

Managing the service

sudo systemctl status nextchat --no-pager | head -12 sudo docker compose -f /var/lib/nextchat/docker-compose.yml logs --tail 40 nextchat

Restart the app with sudo systemctl restart nextchat.service, follow the logs live with sudo docker compose -f /var/lib/nextchat/docker-compose.yml logs -f nextchat, and view the configuration in /var/lib/nextchat/.env. After changing the environment file, restart the service to apply the change.

Conversations are stored in the browser, not on the server, so there is no server-side database to back up. Each user can export their conversations from the NextChat UI.

16

Use your own domain and HTTPS (production)

The image serves the web UI over plain HTTP on port 80, which is convenient behind a load balancer or private network. For production, terminate TLS in front of the VM so that the access code and your conversations travel encrypted:

  • Front the VM with Azure Application Gateway, a load balancer with a managed certificate, or a CDN, and forward to the app on port 80.
  • Keep the NSG rule for port 80 restricted to the front door, not the whole internet.
17

Security recommendations

  • Restrict the NSG. Allow TCP 80 (and 22 for admin) only from the networks that use the app. The access code is the application-level control and the NSG is the network-level one. You want both.
  • Terminate TLS in front of the app (Step 16) so that the access code and conversation content are encrypted in transit.
  • Rotate the access code (Step 13) when someone leaves. To revoke codes individually, issue per-person codes using the comma-separated list.
  • Never remove the access code. The service refuses to start without one, which is deliberate: an ungated instance with a provider key configured is an open relay on your credits.
  • Set a provider key rather than leaving one unset. With the access code accepted but no server-side key configured, NextChat forwards your request to the provider using the access code as the API key, so the code reaches your provider in a failed request. Configure a real key in Step 7, or have each user supply their own in the browser, to avoid that.
  • Consider HIDE_USER_API_KEY=1 if you want everyone to use the server key only and to stop users supplying their own in the browser.
  • Leave MCP disabled unless you specifically need it. Enabling it lets the server execute tools on this VM.
  • Watch your provider spend. The access code protects the key, but anyone holding the code can spend on it. Set a usage limit in your provider's console.
  • Keep the OS patched. Unattended security upgrades remain enabled on the running VM.
18

Support and Licensing

NextChat is distributed under the MIT License. This SecureAnyCloud image bundles the unmodified official open-source release, pinned by image digest. SecureAnyCloud provides the packaging, the per-instance access code generation, the fail-closed service guard, the paired deploy guide, and 24/7 support with a guaranteed 24-hour response SLA.

There is no per-user or per-seat software fee. You pay your own model provider directly for token usage. NextChat is an independent open-source project, and this image is not affiliated with or endorsed by it.

Deploy on Azure

Deploy on Azure

Launch NextChat on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure