Deploying a QQ Bot on Linux with NapCat, NoneBot2, GsCore, and Caddy

Jun 19, 2026

1731 words

9 min read

Tech

Deploying a QQ Bot on Linux with NapCat, NoneBot2, GsCore, and Caddy

I wanted a QQ bot setup on Linux that was not just “working for now,” but stable enough to live with. The stack I ended up with was NapCat + NoneBot2 + GsCore, with Caddy in front for HTTPS and reverse proxying. The overall design is straightforward. The actual deployment, though, was shaped by a series of very real and very ordinary problems: terminal encoding, missing CLI tools, local-only bindings, TLS validation, and even something as basic as forgotten DNS records.

This post turns that whole session into a practical guide you can follow from scratch. More importantly, it includes the failures along the way, because those are usually the parts that save the most time later.

The Final Architecture

This is the message path we ended up with:

QQ / group message
  -> NapCat
  -> NoneBot2
  -> nonebot-plugin-genshinuid
  -> GsCore
  -> actual business plugin (such as GenshinUID)

For admin access from the web, we added another layer:

Browser
  -> Caddy (HTTPS / reverse proxy)
  -> NapCat WebUI / GsCore WebConsole

Two design decisions mattered a lot here:

  1. Treat the bot message path and the admin panel path as separate concerns.
  2. Avoid exposing 6099 and 8765 directly to the public internet when a reverse proxy can keep them behind localhost.

What Each Piece Does

  • NapCat: the protocol side and QQ login layer.
  • NoneBot2: the bot framework itself, responsible for receiving OneBot events and dispatching logic.
  • GsCore: the Sayu core layer that provides the plugin ecosystem and a unified connection model.
  • nonebot-plugin-genshinuid: the official NoneBot2-side plugin used to connect to GsCore.
  • Caddy: the HTTPS and reverse proxy layer for safely exposing the admin panels.

Environment Assumptions

This guide assumes:

  • You are on Ubuntu, Debian, or a similar Linux system.
  • You have root or sudo access.
  • The server has internet access.
  • You have a QQ account ready for bot login.
  • You have a domain name and plan to manage DNS with Cloudflare.

The directory layout in this guide looks like this:

~/qqbot        # NoneBot2 project
~/gsuid_core   # GsCore installation

Step 1: Install NapCat

NapCat provides a Linux installer, and the straightforward way to start is:

curl -o napcat.sh https://nclatest.znin.net/NapNeko/NapCat-Installer/main/script/install.sh
bash napcat.sh --tui

After installation, you can usually re-enter its TUI with:

sudo napcat

Problem 1: NapCat Shell showed garbled text

The first thing we saw was not a clean UI, but broken characters like ~@~A and ~M~U. This turned out not to be a NapCat bug at all. It was a classic locale and terminal issue.

Start by checking:

locale
echo $LANG
echo $TERM

If the environment is not using UTF-8, fix it temporarily before launching NapCat:

export LANG=zh_CN.UTF-8
export LC_ALL=zh_CN.UTF-8
export TERM=xterm-256color
sudo napcat

If the server does not have the locale generated yet, install it:

sudo apt update
sudo apt install -y locales
sudo locale-gen zh_CN.UTF-8
sudo update-locale LANG=zh_CN.UTF-8

If you prefer a neutral UTF-8 setup instead of a Chinese locale, C.UTF-8 works well too:

export LANG=C.UTF-8
export LC_ALL=C.UTF-8
export TERM=xterm-256color
sudo napcat

Log in to NapCat WebUI

NapCat’s WebUI typically listens on port 6099. Once it starts, check the logs for the access URL and token, scan the QQ login QR code, and set a proper WebUI password.

Step 2: Create the NoneBot2 Project

Create a Python virtual environment first:

mkdir -p ~/qqbot
cd ~/qqbot
python -m venv .venv --prompt nonebot2
source .venv/bin/activate

Install the base dependencies:

pip install "nonebot2[fastapi]"
pip install nonebot-adapter-onebot

Create .env:

HOST=127.0.0.1
PORT=8080
ONEBOT_ACCESS_TOKEN=replace-this-with-a-long-random-token
COMMAND_START=["/"]
COMMAND_SEP=["."]

Create bot.py:

import nonebot
from nonebot.adapters.onebot.v11 import Adapter as OneBotV11Adapter

nonebot.init()

driver = nonebot.get_driver()
driver.register_adapter(OneBotV11Adapter)

if __name__ == "__main__":
    nonebot.run()

Test that it starts:

source .venv/bin/activate
python bot.py

Step 3: Connect NapCat to NoneBot2

In NapCat, create a new WebSocket client network connection and point it to:

ws://127.0.0.1:8080/onebot/v11/ws

Use the same token value as ONEBOT_ACCESS_TOKEN in your .env file.

If you get a 403, the first thing to verify is the token value. A mismatch there is by far the most common cause.

Step 4: Install GsCore

According to the official GsCore docs, install it as a separate project next to your bot:

cd ~
git clone https://github.com/Genshin-bots/gsuid_core.git --depth=1 --single-branch
cd gsuid_core

The official docs recommend uv:

uv python install 3.13
uv sync --python 3.13
uv run python -m ensurepip

Start GsCore:

uv run core

On first startup, it will generate:

~/gsuid_core/data/config.json
~/gsuid_core/data/core_config.json

Step 5: Connect GsCore to NoneBot2

The official NoneBot2-side connector for GsCore is nonebot-plugin-genshinuid.

Problem 2: nb was not found

We initially followed the official pattern and ran:

nb plugin install nonebot-plugin-genshinuid

That failed with:

-bash: nb: command not found

Nothing was wrong with the project. The issue was simply that nb-cli was not installed. The nb command is not part of Python itself; it belongs to the NoneBot CLI.

There are two reasonable fixes.

Fix A: install nb-cli

cd ~/qqbot
source .venv/bin/activate
pip install nb-cli
nb plugin install nonebot-plugin-genshinuid

Fix B: install the plugin directly with pip

If you already manage your bot with a hand-written bot.py, direct installation is often simpler:

cd ~/qqbot
source .venv/bin/activate
pip install nonebot-plugin-genshinuid

That second route is the one I would recommend in this setup. It is shorter and avoids pulling in an extra layer unless you actually want the CLI workflow.

Add GsCore settings to the NoneBot .env

Append these values to your existing .env:

gsuid_core_ws_token=123
gsuid_core_host=localhost
gsuid_core_port=8765
gsuid_core_botid=NoneBot2

Configure config.json on the GsCore side

Edit:

~/gsuid_core/data/config.json

At minimum, make sure these fields are set:

{
  "HOST": "localhost",
  "PORT": "8765",
  "masters": ["your-qq-number"],
  "WS_TOKEN": "123",
  "TRUSTED_IPS": ["127.0.0.1"]
}

Two details matter here:

  • WS_TOKEN must match gsuid_core_ws_token in the NoneBot .env
  • masters should include your own QQ number, or a lot of core admin commands will not work for you later

Step 6: Install the Actual Business Plugin

GsCore by itself is the platform layer. The actual bot features come from business plugins.

Once your account is listed in masters, you can install one from chat:

core安装插件GenshinUID

If you prefer manual installation:

cd ~/gsuid_core/plugins
git clone -b v4 https://github.com/KimigaiiWuyi/GenshinUID.git --depth=1 --single-branch

Restart GsCore once the plugin is installed.

Step 7: Access the GsCore Admin Panel

GsCore’s admin backend is the Web Console, usually available at:

http://127.0.0.1:8765/app

That is enough if you are accessing it locally.

Problem 3: public access returned 502

When we tried to access the admin panel from the public internet, we ran into HTTP ERROR 502. The clue came from GsCore itself:

WebConsole挂载于本地, 如想外网访问请修改data/config.json中host为0.0.0.0!

That message means the web console is bound to localhost only, which is why outside access fails. The most direct fix is to change:

"HOST": "localhost"

to:

"HOST": "0.0.0.0"

That said, exposing 8765 directly is not the design I would recommend. A better pattern is to keep GsCore bound to localhost and let a reverse proxy expose it safely.

Step 8: Put Caddy in Front of NapCat and GsCore

Operationally, it is cleaner not to expose 6099 and 8765 to the public internet at all. The safer layout is:

  1. Keep NapCat WebUI and GsCore WebConsole bound to localhost
  2. Expose only 80 and 443
  3. Let Caddy handle HTTPS and reverse proxying

Install Caddy

On Ubuntu or Debian, one clean installation route is:

sudo apt update
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install -y caddy

Keep the backend services local:

  • NapCat WebUI -> 127.0.0.1:6099
  • GsCore WebConsole -> 127.0.0.1:8765

Then configure /etc/caddy/Caddyfile like this:

{
	email yourmail@example.com
}

napcat.example.com {
	reverse_proxy 127.0.0.1:6099
}

gscore.example.com {
	reverse_proxy 127.0.0.1:8765
}

Format, validate, and reload:

sudo caddy fmt --overwrite /etc/caddy/Caddyfile
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl restart caddy
sudo systemctl status caddy

Step 9: Problem 4 — Caddy could not obtain certificates

Caddy itself started just fine, but the logs kept showing challenge failed. The root cause was not in Caddy at all. I had forgotten to add the DNS records in Cloudflare.

That type of failure usually comes down to one of these:

  1. The domain does not actually resolve to this server
  2. Ports 80 and 443 are not publicly reachable
  3. Another service is already occupying 80 or 443
  4. DNS changes have not propagated yet

Useful troubleshooting commands

Check recent Caddy logs:

sudo journalctl -u caddy -n 80 --no-pager

Check DNS resolution:

dig +short napcat.your-domain.com A
dig +short gscore.your-domain.com A

Check what is listening:

ss -lntp | grep ':80\|:443'

Check the firewall:

sudo ufw status

If the DNS record simply does not exist in Cloudflare, ACME validation obviously cannot succeed. It is a basic mistake, but also an extremely common one.

The Security Posture I Would Actually Recommend

If the admin panels are mostly for your own use, I would tighten things up this way:

  1. Do not expose 6099 or 8765 directly to the internet
  2. Expose only 80 and 443
  3. Keep NapCat and GsCore bound to localhost
  4. Put Caddy in front of them
  5. Keep strong passwords and tokens on the panels themselves
  6. Add IP allowlisting or another auth layer if you can

One point is worth stating explicitly: your email address is not an authentication mechanism. It is only a contact address for certificate issuance. The things that actually protect your admin panels are HTTPS, reverse proxy boundaries, panel passwords, tokens, and whether you avoided exposing the raw upstream ports.

Once everything is deployed, I recommend starting and debugging in this order:

  1. NapCat
  2. NoneBot2
  3. GsCore
  4. business plugin
  5. Caddy

And when something breaks, trace the same chain in order:

Is QQ login working?
-> Is the OneBot WebSocket connected?
-> Is NoneBot running?
-> Is GsCore connected?
-> Is the plugin loaded?
-> Is the admin panel reachable?
-> Are HTTPS, DNS, and Caddy behaving correctly?

The Four Things Most Worth Remembering

1. Garbled TUI output usually means the terminal environment is wrong

Check LANG, LC_ALL, and TERM before assuming the software is broken.

2. A missing nb command usually just means nb-cli is not installed

And if you are already managing your bot with a custom bot.py, direct pip install may be the simpler path anyway.

3. If a backend is bound to localhost, public access is supposed to fail

Once you see localhost bindings or “mounted locally” warnings, stop guessing at public-network issues and fix the binding model first.

4. TLS failures are often not “proxy problems” at all

Even in a stack with reverse proxies, certificates, and HTTPS, the most common failure point is still the boring layer underneath: DNS.

References

Closing Thoughts

By the end of this deployment, the biggest takeaway was not “the bot is finally installed.” It was that the hard part of this kind of work is rarely the install commands themselves. What determines whether the setup stays pleasant later is whether the boundaries are clear: whether the protocol layer is separated from the framework, whether admin surfaces are exposed carelessly, whether the domains really point where you think they do, and whether the logs tell you where the failure actually is.

Once those boundaries are in place, NapCat + NoneBot2 + GsCore + Caddy is a very workable combination. It is not the lightest stack, and it is not the flashiest one either, but it is clear, stable, and perfectly suited to long-term maintenance.

Deploying a QQ Bot on Linux with NapCat, NoneBot2, GsCore, and Caddy
https://blog.shishishi3.com/en/blog/linux-qq-bot-napcat-nonebot-gscore-caddy/
Author
豕豕豕
Published on
Jun 19, 2026
License
CC BY-NC-SA 4.0

Open the RSS Feed

RSS is an XML feed meant for feed readers, so opening it in a browser usually shows raw XML. That is expected behavior.

To reduce accidental clicks and misleading redirects, the feed link is only enabled after a 5 second confirmation countdown.

Enter keywords to start searching