Telegram-Bot per Webhook mit Node.js, TypeScript, grammY, Nginx, SSL und systemd deployen
Ein praxisnahes Tutorial für 2026, um einen Telegram-Bot auf einem VPS per Webhook mit Node.js, TypeScript, grammY, Nginx, Let's Encrypt SSL und systemd zu deployen. Mit funktionierendem Code, Webhook-Registrierung und finalen Checks.

Kurz gesagt
Dieses Tutorial führt auf ein klares Ziel hin: ein Telegram-Bot läuft auf einem VPS mit echtem Webhook, per HTTPS erreichbar, und startet nach einem Server-Neustart automatisch wieder.
127.0.0.1Starte in einem sauberen Verzeichnis auf dem VPS. Die Anwendung läuft mit TypeScript, grammY übernimmt Telegram, und Express dient hier nur als dünne HTTP-Schicht für den Webhook. Einen zusätzlichen Process Manager neben systemd brauchst du nicht.
Lege das Projekt an und installiere die Abhängigkeiten:
mkdir -p ~/apps/telegram-bot-webhook
cd ~/apps/telegram-bot-webhook
npm init -y
npm pkg set scripts.build="tsc -p tsconfig.json"
npm pkg set scripts.start="node dist/server.js"
npm install grammy express dotenv
npm install -D typescript @types/node @types/expressLege die Verzeichnisstruktur an:
mkdir -p src
touch tsconfig.json
touch .env.example
touch src/bot.ts
touch src/server.tsNutze diese tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "CommonJS",
"moduleResolution": "Node",
"rootDir": "src",
"outDir": "dist",
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true
},
"include": ["src/**/*.ts"]
}Nutze diese .env.example:
HOST=127.0.0.1
PORT=3000
BOT_TOKEN=replace_with_your_bot_token
WEBHOOK_SECRET=replace_with_a_long_random_secret
WEBHOOK_PATH=/telegram/webhookWarum genau so
So bleibt der Deploy nachvollziehbar. Der Code bleibt kompakt, die Runtime-Abhängigkeiten bleiben übersichtlich, und das Server-Setup passt gut dazu, wie Linux-Dienste ohnehin betrieben werden.
Die Bot-Logik und der HTTP-Einstiegspunkt liegen in zwei getrennten Dateien. So vermischt sich die Webhook-Transportebene nicht mit den Kommandos und der eigentlichen Bot-Logik.
Nutze diese src/bot.ts:
import { Bot } from "grammy";
const botToken = process.env.BOT_TOKEN;
if (!botToken) {
throw new Error("BOT_TOKEN is required");
}
export const bot = new Bot(botToken);
bot.command("start", async (ctx) => {
await ctx.reply("Webhook is live. Your TypeScript bot is running.");
});
bot.command("ping", async (ctx) => {
await ctx.reply("pong");
});
bot.on("message:text", async (ctx) => {
if (ctx.message.text.startsWith("/")) {
return;
}
await ctx.reply("You said: " + ctx.message.text);
});Nutze diese src/server.ts:
import "dotenv/config";
import express, { type NextFunction, type Request, type Response } from "express";
import { webhookCallback } from "grammy";
import { bot } from "./bot";
const host = process.env.HOST || "127.0.0.1";
const port = Number(process.env.PORT || 3000);
const webhookPath = process.env.WEBHOOK_PATH || "/telegram/webhook";
const webhookSecret = process.env.WEBHOOK_SECRET;
if (!webhookSecret) {
throw new Error("WEBHOOK_SECRET is required");
}
const app = express();
app.disable("x-powered-by");
app.use(express.json({ limit: "1mb" }));
app.get("/healthz", (_req: Request, res: Response) => {
res.status(200).json({ ok: true });
});
function verifyTelegramSecret(req: Request, res: Response, next: NextFunction): void {
const secret = req.header("x-telegram-bot-api-secret-token");
if (secret !== webhookSecret) {
res.status(403).json({ ok: false });
return;
}
next();
}
app.post(
webhookPath,
verifyTelegramSecret,
webhookCallback(bot, "express")
);
app.use((_req: Request, res: Response) => {
res.status(404).json({ ok: false });
});
async function start(): Promise<void> {
await bot.init();
app.listen(port, host, () => {
console.log("Telegram webhook app listening on http://" + host + ":" + port + webhookPath);
});
}
start().catch((error) => {
console.error("Failed to start bot:", error);
process.exit(1);
});Der Code macht vier wichtige Dinge: Die App bleibt auf localhost, es gibt einen Health Endpoint, der geheime Telegram-Header wird geprüft, bevor die Middleware läuft, und die Bot-Logik basiert auf grammY statt auf manuell verarbeitetem Update-Rohmaterial.
Was daran schon produktionsnah ist
Der Bot-Prozess bleibt privat, der Webhook-Pfad ist eindeutig, und Telegram-Anfragen werden abgewiesen, wenn der secret token nicht passt.
Bevor du Nginx oder Zertifikate anfasst, solltest du sicherstellen, dass die App selbst lokal auf dem VPS sauber läuft.
Die echte Environment-Datei anlegen
Kopiere .env.example nach .env und trage den Bot-Token sowie einen langen zufälligen Webhook-Secret ein.
Das Projekt bauen
Führe npm run build aus, damit der kompilierte TypeScript-Code unter dist/ landet.
Die gebaute App einmal starten
Starte npm start und prüfe, dass der Prozess ohne Konfigurationsfehler hochkommt.
Den lokalen Health Endpoint prüfen
Führe curl http://127.0.0.1:3000/healthz aus und prüfe, dass die Antwort { "ok": true } enthält.
Warum diese Reihenfolge wichtig ist
Wenn die lokale App schon kaputt ist, machen Nginx und SSL die Fehlersuche nur unnötig komplizierter.
Nutze den nativen Linux Service Manager, der ohnehin schon auf dem Server vorhanden ist, statt mit PM2 noch ein weiteres bewegliches Teil einzubauen. Für einen einzelnen Bot-Prozess reicht die folgende Service-Datei völlig aus.
Erstelle eine Datei namens telegram-bot-webhook.service:
[Unit]
Description=Telegram Bot Webhook
After=network.target
Wants=network.target
[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/apps/telegram-bot-webhook
Environment=NODE_ENV=production
EnvironmentFile=/home/ubuntu/apps/telegram-bot-webhook/.env
ExecStart=/usr/bin/node /home/ubuntu/apps/telegram-bot-webhook/dist/server.js
Restart=always
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.targetInstalliere und starte den Service:
sudo cp telegram-bot-webhook.service /etc/systemd/system/telegram-bot-webhook.service
sudo systemctl daemon-reload
sudo systemctl enable --now telegram-bot-webhook
sudo systemctl status telegram-bot-webhookLies die Logs mit:
journalctl -u telegram-bot-webhook -fPasse User, WorkingDirectory, EnvironmentFile und ExecStart an deine Serverstruktur an. Kopiere den Pfad /home/ubuntu/... nicht blind, wenn dein Setup anders aussieht.
Stand nach diesem Schritt
Der Bot läuft jetzt als normaler Linux-Dienst und startet nach einem Reboot ohne zusätzlichen Node-spezifischen Process Manager wieder.
Starte zunächst mit einer HTTP-Konfiguration. Danach kann Certbot die vorhandene Nginx-Site nutzen und HTTPS für dich ergänzen.
Erstelle eine Nginx-Site, zum Beispiel unter /etc/nginx/sites-available/bot.example.com:
server {
listen 80;
listen [::]:80;
server_name bot.example.com;
location = /healthz {
proxy_pass http://127.0.0.1:3000/healthz;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
location = /telegram/webhook {
proxy_pass http://127.0.0.1:3000/telegram/webhook;
proxy_http_version 1.1;
proxy_connect_timeout 5s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
return 404;
}
}Aktiviere die Site und lade Nginx neu:
sudo ln -s /etc/nginx/sites-available/bot.example.com /etc/nginx/sites-enabled/bot.example.com
sudo nginx -t
sudo systemctl reload nginxDer Pfad in Nginx muss mit dem Pfad in .env und mit dem Pfad übereinstimmen, den du später bei Telegram registrierst. Wenn einer dieser Pfade abweicht, schlägt die Webhook-Zustellung fehl.
Die öffentliche Kante sollte langweilig sein
Öffentlich erreichbar sollte nur Nginx sein. Die Node.js-App bleibt auf 127.0.0.1.
Zu diesem Zeitpunkt sollte DNS bereits bot.example.com auf die öffentliche IP des VPS zeigen, und Port 80 muss aus dem Internet erreichbar sein.
Installiere Certbot und bereite den Befehl vor:
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/local/bin/certbotStelle das Zertifikat über den Nginx-Flow aus:
sudo certbot --nginx -d bot.example.comTeste die automatische Verlängerung:
sudo certbot renew --dry-runPrüfe den öffentlichen Health Endpoint:
curl https://bot.example.com/healthzRegistriere den Telegram-Webhook erst dann, wenn das per HTTPS funktioniert.
Was Certbot dir hier abnimmt
Ein gültiges Zertifikat, eine HTTPS-fähige Nginx-Site und ein deutlich saubererer erster Deploy als eine manuelle TLS-Konfiguration von Hand.
Jetzt kannst du Telegram die endgültige öffentliche URL mitteilen. Das Secret in setWebhook muss exakt mit dem Secret in .env übereinstimmen.
Registriere den Webhook:
export BOT_TOKEN=replace_with_your_bot_token
curl -X POST "https://api.telegram.org/bot$BOT_TOKEN/setWebhook" \
-d "url=https://bot.example.com/telegram/webhook" \
-d "secret_token=replace_with_a_long_random_secret" \
-d 'allowed_updates=["message"]'Prüfe den Webhook-Status:
curl "https://api.telegram.org/bot$BOT_TOKEN/getWebhookInfo"Wenn alles stimmt, zeigt das Feld url deinen HTTPS-Endpunkt, und last_error_message ist leer oder gar nicht vorhanden.
Die zwei Werte, die übereinstimmen müssen
Webhook-Pfad und Webhook-Secret müssen in App, Nginx und Telegram identisch sein.
Gehe diese Prüfungen in genau dieser Reihenfolge durch, damit du sofort siehst, auf welcher Ebene ein Fehler beginnt.
Der systemd-Service ist aktiv
Prüfe zuerst systemctl status telegram-bot-webhook, bevor du Nginx oder Telegram verdächtigst.
Der lokale Health Endpoint funktioniert
Prüfe curl http://127.0.0.1:3000/healthz direkt auf dem Server.
Der öffentliche HTTPS Health Endpoint funktioniert
Prüfe curl https://bot.example.com/healthz außerhalb des App-Prozesses.
Die Webhook-Info ist sauber
Nutze getWebhookInfo und prüfe, dass die URL stimmt und keine frische Zustellungsfehlermeldung vorliegt.
Telegram-Kommandos lösen wirklich Antworten aus
Sende nach der Aktivierung des Webhooks /start und /ping an den Bot.
Die Logs bleiben bei einer echten Anfrage nachvollziehbar
Beobachte journalctl -u telegram-bot-webhook -f, während du dem Bot eine Nachricht sendest.
Woran du Erfolg erkennst
Eine Nachricht aus Telegram erreicht den Bot über HTTPS, die App antwortet, und der Linux-Dienst läuft stabil ohne manuelles Eingreifen weiter.
Die meisten Deploy-Probleme hier lassen sich auf ein paar wiederkehrende Widersprüche zurückführen.
Der Bot wird nur mit npm start in einer SSH-Sitzung gestartet, statt als richtiger Linux-Dienst zu laufen.
Die App bindet an ein öffentliches Interface statt an 127.0.0.1.
In .env, Nginx und setWebhook werden unterschiedliche Webhook-Pfade verwendet.
In .env und beim Aufruf von setWebhook werden unterschiedliche secret tokens verwendet.
Der lokale Health Check wird übersprungen, und stattdessen wird versucht, den gesamten Stack auf einmal zu debuggen.
Der Webhook wird registriert, bevor HTTPS funktioniert.
Die Nginx-Konfiguration wird geändert und neu geladen, ohne vorher nginx -t auszuführen.
Es wird versucht, getUpdates zu verwenden, obwohl der ausgehende Webhook bereits aktiv ist.
Was fast immer dahintersteckt
Falsche Reihenfolge, falscher Pfad oder falsches Secret sind in diesem Setup fast immer die eigentliche Ursache.
Weil auf dem Server bereits ein nativer Linux Service Manager vorhanden ist. Für einen einzelnen Bot-Prozess reicht systemd aus, um die App automatisch zu starten, nach Fehlern neu zu starten und nach einem Reboot wieder hochzufahren.
Weil grammY das Telegram-Update-Modell sauber abbildet und dir Kommandos sowie Middleware gibt. Dadurch bleibt das Tutorial praktisch und versinkt nicht an jeder Stelle in manueller Bot-API-Verkabelung.
Ja, für diese Art von Deploy schon. Nginx ist die öffentliche HTTPS-Kante, während die App privat auf localhost bleibt. So landen TLS-Terminierung und öffentliches Routing nicht im Bot-Prozess selbst.
Nein. Sobald ein ausgehender Webhook aktiv ist, erlaubt Telegram diesem Bot keine Updates mehr über `getUpdates`.
Ja, das geht. Für einen neuen Node.js-Bot im Jahr 2026 ist TypeScript aber meist die stabilere Ausgangsbasis, sobald das Projekt über ein kleines Demo hinauswächst.
Diese Quellen stützen die verwendeten Werkzeuge, das Webhook-Verhalten und den Linux-Service-Ansatz aus diesem Tutorial.
PAS7 Studio hilft Teams dabei, aus einem minimalen Tutorial-Bot ein belastbares Produktionssystem zu machen: stabiles Routing, Telegram-Integrationen, Admin-Panels, Observability, Hintergrundjobs, Message Flows und Server-Hardening über den gesamten Stack hinweg.
Verwandte Artikel
AI SEO / GEO im Jahr 2026: Ihre nächsten Kunden sind nicht Menschen — sondern Agents
Suche verschiebt sich von Klicks zu Antworten. Bots und AI-Agents crawlen, zitieren, empfehlen — und kaufen zunehmend. Erfahren Sie, was AI SEO / GEO bedeutet, warum klassisches SEO nicht mehr reicht und wie PAS7 Studio Marken im agentischen Web sichtbar macht.
Der leistungsstärkste Chip von Apple? M5 Pro und M5 Max brechen Rekorde
Eine Analyse zu Apple M5 Pro und M5 Max im März 2026. Wir zeigen, warum diese Chips als die stärksten professionellen Laptop-SoCs von Apple gelten können, wie sie sich gegen M4 Pro, M4 Max, M1 Pro, M1 Max schlagen und was der Vergleich mit aktuellen Intel- und AMD-Chips zeigt.
Artemis II und der Code, der Menschen zum Mond trägt
Dieser Beitrag erklärt die NASA-Mission Artemis II, die am 1. April 2026 gestartet ist, und zeigt, was sie wirklich über moderne Technik erzählt: Flugsoftware, Backup-Logik, Simulationen, Telemetrie, menschliche Kontrolle und die vorsichtige Rolle von KI in Raumfahrtsystemen.
Automatisches Tagging und Suche für gespeicherte Links
Integration mit GDrive/S3/Notion für automatisches Tagging und schnelle Suche über Such-APIs
Professionelle Entwicklung für Ihr Geschäft
Wir erstellen moderne Web-Lösungen und Bots für Unternehmen. Erfahren Sie, wie wir Ihnen helfen können, Ihre Ziele zu erreichen.