PAS7 Studio
Zurück zu allen Artikeln

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.

08. Apr. 2026· 8 Min. Lesezeit· Anleitung
Geeignet fürBackend-EntwicklerNode.js-EntwicklerEntwickler, die ihren ersten Telegram-Bot auf einem VPS deployenTeams, die einen Bot von lokal nach Produktion bringenFounder, die einen einfachen Deploy ohne unnötige Zusatzschichten wollen
Telegram-Bot auf einem VPS mit Node.js, TypeScript, grammY, Nginx, SSL und systemd deployed

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.

Ein Telegram-Bot in TypeScript mit grammY
Ein lokaler App-Prozess auf 127.0.0.1
Ein systemd-Service, der Neustarts überlebt
Ein Nginx Reverse Proxy als einziger öffentlicher Einstiegspunkt
Ein Let's Encrypt-Zertifikat über Certbot
Ein Telegram-Webhook mit secret token

Starte 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:

BASH
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/express

Lege die Verzeichnisstruktur an:

BASH
mkdir -p src
touch tsconfig.json
touch .env.example
touch src/bot.ts
touch src/server.ts

Nutze diese tsconfig.json:

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:

DOTENV
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/webhook

Warum 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:

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:

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.

01

Die echte Environment-Datei anlegen

Kopiere .env.example nach .env und trage den Bot-Token sowie einen langen zufälligen Webhook-Secret ein.

02

Das Projekt bauen

Führe npm run build aus, damit der kompilierte TypeScript-Code unter dist/ landet.

03

Die gebaute App einmal starten

Starte npm start und prüfe, dass der Prozess ohne Konfigurationsfehler hochkommt.

04

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:

INI
[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.target

Installiere und starte den Service:

BASH
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-webhook

Lies die Logs mit:

BASH
journalctl -u telegram-bot-webhook -f

Passe 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:

NGINX
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:

BASH
sudo ln -s /etc/nginx/sites-available/bot.example.com /etc/nginx/sites-enabled/bot.example.com
sudo nginx -t
sudo systemctl reload nginx

Der 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:

BASH
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/local/bin/certbot

Stelle das Zertifikat über den Nginx-Flow aus:

BASH
sudo certbot --nginx -d bot.example.com

Teste die automatische Verlängerung:

BASH
sudo certbot renew --dry-run

Prüfe den öffentlichen Health Endpoint:

BASH
curl https://bot.example.com/healthz

Registriere 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:

BASH
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:

BASH
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.

Warum wird in diesem Setup systemd statt PM2 verwendet?

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.

Warum lieber grammY statt Telegram-Updates selbst roh zu verarbeiten?

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.

Brauche ich Nginx, wenn die App ohnehin auf einem Port lauscht?

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.

Kann ich getUpdates und einen Webhook gleichzeitig nutzen?

Nein. Sobald ein ausgehender Webhook aktiv ist, erlaubt Telegram diesem Bot keine Updates mehr über `getUpdates`.

Kann ich das auch in normalem JavaScript statt TypeScript schreiben?

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.

Geprüft: 08. Apr. 2026Gilt für: Telegram Bot API webhooksGilt für: Single-server VPS deploymentsGilt für: Node.js- und TypeScript-AnwendungenGilt für: Telegram-Bots mit grammYGilt für: Nginx Reverse Proxy mit Let's EncryptGilt für: Linux-Server mit systemdGetestet mit: Node.js 24 LTSGetestet mit: TypeScriptGetestet mit: grammY webhook handlingGetestet mit: Express 5 request adapterGetestet mit: Nginx reverse proxyGetestet mit: Certbot for NginxGetestet mit: systemd service unitGetestet mit: Telegram Bot API `setWebhook`Getestet mit: Telegram Bot API `getWebhookInfo`

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

growth

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.

blogs

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.

blogs

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.

telegram-media-saver

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.