DEV Community

VPS KUB
VPS KUB

Posted on

รัน Discord Bot (discord.js v14) บน Linux VPS ให้ออนไลน์ 24 ชม. ด้วย systemd — ทีละขั้นแบบใช้ได้จริง

หลายคนเริ่มเขียน Discord Bot บนเครื่องตัวเอง พอปิดคอม บอทก็ offline ทันที บทความนี้จะพาย้ายบอทขึ้น Linux VPS (Ubuntu) แล้วตั้งให้รันเป็น systemd service — บอทจะ restart เองเมื่อ crash และกลับมาออนไลน์เองหลัง reboot โดยไม่ต้องพึ่ง tool เสริม

คำสั่งทั้งหมดเขียนสำหรับ Ubuntu 22.04/24.04 ครับ

สิ่งที่ต้องมี

  • Linux VPS 1 เครื่อง (Ubuntu) ที่เข้า SSH ได้ บอทเล็ก ๆ ใช้ทรัพยากรน้อยมาก สเปกเริ่มต้นของผู้ให้บริการส่วนใหญ่ก็พอ ถ้าแค่อยากลองก่อนโดยยังไม่ผูกรายเดือน มีผู้ให้บริการที่ให้เช่าแบบรายวัน เช่น VPSKUB (เช่ารายวัน/รายสัปดาห์/รายเดือน มี Ubuntu ให้เลือก) หรือใช้เจ้าไหนก็ได้ที่คุ้นเคย
  • Discord Application + Bot Token จาก Discord Developer Portal
  • Client ID ของแอป และ Guild ID (Server ID) ของเซิร์ฟเวอร์ที่ใช้ทดสอบ (เปิด Developer Mode ใน Discord แล้วคลิกขวาที่เซิร์ฟเวอร์ → Copy Server ID)

1) เตรียมเครื่อง: update + firewall

SSH เข้าเครื่อง (เปลี่ยน YOUR_SERVER_IP เป็น IP ของคุณ):

ssh root@YOUR_SERVER_IP
Enter fullscreen mode Exit fullscreen mode

อัปเดตแพ็กเกจและเปิด firewall แบบอนุญาตเฉพาะ SSH:

apt update && apt upgrade -y
apt install -y ufw git curl
ufw allow OpenSSH
ufw enable
ufw status
Enter fullscreen mode Exit fullscreen mode

Discord Bot ทั่วไปเชื่อมต่อ ขาออก ไปยัง Discord Gateway เท่านั้น จึงไม่ต้องเปิด port ขาเข้าเพิ่ม (ยกเว้นคุณทำ web dashboard หรือ webhook server เอง)

2) สร้าง user แยกสำหรับรันบอท

อย่ารันบอทด้วย root — ถ้าโค้ดหรือ dependency มีช่องโหว่ ความเสียหายจะจำกัดอยู่ที่ user นี้:

adduser --system --group --home /opt/mybot mybot
Enter fullscreen mode Exit fullscreen mode

3) ติดตั้ง Node.js LTS

ใช้ repository ของ NodeSource (Node 22 LTS):

curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
apt install -y nodejs
node -v
npm -v
Enter fullscreen mode Exit fullscreen mode

4) สร้างโปรเจกต์บอท

cd /opt/mybot
sudo -u mybot npm init -y
sudo -u mybot npm install discord.js
Enter fullscreen mode Exit fullscreen mode

สร้างไฟล์ .env เก็บ secret (ห้าม commit ขึ้น Git):

sudo -u mybot tee /opt/mybot/.env > /dev/null <<'ENV'
DISCORD_TOKEN=ใส่_bot_token_ตรงนี้
CLIENT_ID=ใส่_application_id
GUILD_ID=ใส่_server_id
ENV
chmod 600 /opt/mybot/.env
Enter fullscreen mode Exit fullscreen mode

index.js — ตัวบอท

// /opt/mybot/index.js
const { Client, Events, GatewayIntentBits } = require('discord.js');

const client = new Client({ intents: [GatewayIntentBits.Guilds] });

client.once(Events.ClientReady, (c) => {
  console.log(`Ready! Logged in as ${c.user.tag}`);
});

client.on(Events.InteractionCreate, async (interaction) => {
  if (!interaction.isChatInputCommand()) return;
  if (interaction.commandName === 'ping') {
    await interaction.reply(`Pong! 🏓 (${client.ws.ping} ms)`);
  }
});

client.login(process.env.DISCORD_TOKEN);
Enter fullscreen mode Exit fullscreen mode

deploy-commands.js — ลงทะเบียน slash command

// /opt/mybot/deploy-commands.js
const { REST, Routes, SlashCommandBuilder } = require('discord.js');

const commands = [
  new SlashCommandBuilder().setName('ping').setDescription('เช็คว่าบอทยังอยู่ไหม'),
].map((c) => c.toJSON());

const rest = new REST().setToken(process.env.DISCORD_TOKEN);

(async () => {
  await rest.put(
    Routes.applicationGuildCommands(process.env.CLIENT_ID, process.env.GUILD_ID),
    { body: commands },
  );
  console.log('Slash commands registered.');
})();
Enter fullscreen mode Exit fullscreen mode

ให้สิทธิ์ไฟล์แก่ user mybot แล้วทดสอบ (Node 20.6+ อ่าน .env ได้เองด้วย --env-file):

chown mybot:mybot /opt/mybot/*.js
cd /opt/mybot
sudo -u mybot node --env-file=.env deploy-commands.js
sudo -u mybot node --env-file=.env index.js
Enter fullscreen mode Exit fullscreen mode

ถ้าเห็น Ready! Logged in as ... ลองพิมพ์ /ping ใน Discord ได้เลย จากนั้นกด Ctrl+C เพื่อหยุด — ขั้นต่อไปเราจะให้ systemd ดูแลแทน

5) ตั้งเป็น systemd service

tee /etc/systemd/system/mybot.service > /dev/null <<'UNIT'
[Unit]
Description=My Discord Bot
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=mybot
WorkingDirectory=/opt/mybot
EnvironmentFile=/opt/mybot/.env
ExecStart=/usr/bin/node index.js
Restart=on-failure
RestartSec=5
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target
UNIT

systemctl daemon-reload
systemctl enable --now mybot
systemctl status mybot
Enter fullscreen mode Exit fullscreen mode

Restart=on-failure = crash แล้วเริ่มใหม่ใน 5 วินาที, enable = เปิดเองหลัง reboot

6) ดู log และอัปเดตโค้ด

journalctl -u mybot -f          # ดู log แบบ real-time
journalctl -u mybot --since "1 hour ago"
systemctl restart mybot         # หลังแก้โค้ด
Enter fullscreen mode Exit fullscreen mode

ถ้าเก็บโค้ดบน GitHub ขั้นตอน deploy ง่าย ๆ คือ:

cd /opt/mybot
sudo -u mybot git pull
sudo -u mybot npm ci --omit=dev
systemctl restart mybot
Enter fullscreen mode Exit fullscreen mode

Troubleshooting ที่เจอบ่อย

อาการ สาเหตุ / วิธีแก้
TokenInvalid / An invalid token was provided token ผิด หรือเพิ่ง reset token ใน Developer Portal → แก้ .env แล้ว systemctl restart mybot
พิมพ์ /ping แล้วไม่ขึ้นคำสั่ง ยังไม่ได้รัน deploy-commands.js หรือ GUILD_ID ผิด
Used disallowed intents ใช้ intent อย่าง MessageContent หรือ GuildMembers แต่ยังไม่ได้เปิดในหน้า Bot ของ Developer Portal
service ขึ้น failed ทันที ดู journalctl -u mybot -n 50 ส่วนใหญ่คือ path ผิด หรือ user mybot ไม่มีสิทธิ์อ่านไฟล์

Security checklist สั้น ๆ

  • ใช้ SSH key แทนรหัสผ่าน แล้วตั้ง PasswordAuthentication no ใน /etc/ssh/sshd_config
  • chmod 600 .env และใส่ .env ใน .gitignore เสมอ — token หลุดขึ้น GitHub สาธารณะคือเคสที่เจอบ่อยมาก
  • ติดตั้ง security update อัตโนมัติ: apt install -y unattended-upgrades
  • ถ้า token เคยหลุด ให้กด Reset Token ทันที

สรุป

แค่ Ubuntu + Node.js + systemd ก็ได้บอทที่ออนไลน์ 24/7, restart ตัวเองเมื่อ crash และดู log ได้ด้วย journalctl โดยไม่ต้องติดตั้ง process manager เพิ่ม วิธีเดียวกันนี้ใช้กับบอท Python (discord.py) ได้เลย แค่เปลี่ยน ExecStart เป็น path ของ Python ใน virtualenv

อ่านต่อ (ภาษาไทย): การเลือกสเปก VPS สำหรับ Discord Bot ตามขนาดบอท


Disclosure: ผมเป็นผู้ดูแล VPSKUB ที่ลิงก์ไว้ในบทความ แต่ขั้นตอนทั้งหมดใช้ได้กับ Ubuntu VPS ทุกเจ้า

Top comments (0)