Discord Bots

Why Your Discord Bot Keeps Disconnecting and How to Fix It

Tech Setup6 min read
TS

Tech Setup

Published August 19, 2026 · Editorial policy

Why Your Discord Bot Keeps Disconnecting and How to Fix It

You just pushed a new feature to your Discord bot, only to watch it cycle through online and offline states every few minutes like clockwork. If you are a backend developer maintaining a custom moderation or utility bot on Node.js and discord.js v14, this silent gateway drop is the exact friction keeping your service from production stability. By the time you finish this guide, you will isolate whether the disconnect stems from heartbeat timeout, invalid intents, or memory leaks, and deploy a hardened connection loop that stays online.

Quick TL;DR: The 60-Second Fixes

If your bot drops connection specifically after 45 seconds to a couple of minutes, or fails to reconnect after a host migration, check these three items before rewriting your gateway logic.

1. Enable Required Privileged Intents

Go to the Discord Developer Portal, select your application, navigate to the Bot tab, and scroll down to Privileged Gateway Intents. If your code reads message content, tracks member joins, or manages guild presence, you must toggle these switches.

// Example of a minimal intents configuration in discord.js v14
{
  "intents": [
    "Guilds",
    "GuildMessages",
    "MessageContent"
  ]
}

Failing to toggle these in the portal while requesting them in code will cause Discord's gateway to forcefully close your WebSocket connection with close code 4014 (Disallowed Intents).

2. Update to discord.js v14.14+ or Equivalent Library Version

Older versions of wrapper libraries often suffer from stale dependency trees, specifically in @discordjs/ws and ws. Run the following command in your terminal to grab the latest stable patch that addresses recent gateway compression and heartbeat handling updates:

npm install discord.js@latest

Expected output snippet from your terminal:

+ discord.js@14.15.3
updated 1 package and audited 3 packages in 2.150s

3. Check for Multiple Active Instances

If you are running your bot locally via node index.js while a production container on AWS ECS, Fly.io, or Railway is still active using the exact same Bot Token, they will continuously kick each other off the gateway. Terminate all local development processes before deploying to production.

Diagnosing Gateway Close Codes

When a Discord bot disconnects, the WebSocket doesn't just drop—it usually sends a specific close code. If you aren't logging these codes, you are debugging blind. Add this error listener to your client initialization block to capture the exact reason for the termination:

const { Client, GatewayIntentBits } = require('discord.js');

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

client.on('shardError', error => {
  console.error(`A websocket connection encountered an error: ${error}`);
});

client.on('shardDisconnect', (event, shardId) => {
  console.warn(`Shard ${shardId} disconnected. Close code: ${event.code}. Reason: ${event.reason || 'No reason provided'}`);
});

client.login(process.env.DISCORD_TOKEN);

Check your application logs against this lookup table to identify your exact failure vector:

Close CodeMeaningCommon Cause
4004Authentication FailedYour bot token has been reset, or you pasted an invalid string into your .env file.
4010Invalid ShardingYou attempted to scale shards incorrectly or configured an invalid shard count for your guild size.
4014Disallowed IntentsYou requested privileged intents in code that are not enabled in the Discord Developer Portal.
1006Abnormal ClosureNetwork drop, ISP timeout, or your hosting provider killed the TCP connection due to inactivity.

Fixing Heartbeat and Keep-Alive Issues on Cheap VPS Hosts

If your close code is 1006 and it happens randomly throughout the day, your hosting environment is likely starving your Node.js process of CPU or dropping idle TCP connections.

Adjusting Node.js Memory and Garbage Collection Limits

Low-tier cloud virtual private servers (like a $4 DigitalOcean droplet or AWS EC2 t2.micro) frequently throttle CPU credits. When garbage collection spikes, the main thread freezes for more than a few seconds. If the main thread freezes, your bot cannot respond to Discord's heartbeat (HELLO / HEARTBEAT_ACK) cycle, and the gateway severs the socket.

To mitigate this, explicitly set the maximum old space size and enable aggressive garbage collection flags when starting your bot via PM2 or systemd.

Create or update your ecosystem.config.js file for PM2:

module.exports = {
  apps: [
    {
      name: 'discord-bot',
      script: './index.js',
      instances: 1,
      exec_mode: 'fork',
      node_args: '--max-old-space-size=512 --expose-gc',
      env: {
        NODE_ENV: 'production',
      },
    },
  ],
};

Run your application with PM2 using this configuration:

pm2 start ecosystem.config.js

Expected output:

[PM2] Applying action cluster_mode on [discord-bot]
[PM2] [discord-bot] (0) launched (1 pid)
┌────┬────────────────────┬─────────┬──────┬────────┬_────────────────┬────────┐
│ id │ name               │ mode    │ resp │ status │ cpu             │ memory │
├────┼────────────────────┼─────────┼──────┼────────┼─────────────────┼────────┤
│ 0  │ discord-bot        │ forked  │ 0    │ online │ 0%              │ 42.1MB │
└────┴────────────────────┴─────────┴──────┴────────┴_────────────────┴────────┘

Configuring Keep-Alive Pings at the OS Level

If your VPS provider drops idle connections, you need to tell the Linux kernel to send TCP keep-alive packets more frequently. Open your sysctl configuration file:

sudo nano /etc/sysctl.conf

Append these lines to the bottom of the file to force TCP keep-alives every 30 seconds:

net.ipv4.tcp_keepalive_time = 30
net.ipv4.tcp_keepalive_intvl = 10
net.ipv4.tcp_keepalive_probes = 5

Save the file and apply the changes immediately:

sudo sysctl -p

Implementing a Robust Reconnection Manager

Relying entirely on your library's default reconnection handler is a recipe for silent failure during extended Discord API outages. If Discord's gateway experiences a minor degradation, a basic retry loop can exhaust its attempts and leave your process running in a zombie state—connected to the server, but completely dead to Discord.

Write an explicit manager wrapper around your client lifecycle to handle graceful retries with exponential backoff and jitter.

Create a file named client.js in your project root:

const { Client, GatewayIntentBits } = require('discord.js');

function createBotClient() {
  const client = new Client({
    intents: [
      GatewayIntentBits.Guilds,
      GatewayIntentBits.GuildMessages
    ],
    rest: {
      timeout: 15000,
    },
  });

  let reconnectAttempts = 0;
  const maxAttempts = 10;

  client.on('ready', () => {
    console.log(`Logged in successfully as ${client.user.tag}`);
    reconnectAttempts = 0; // Reset counter on successful connection
  });

  client.on('shardDisconnect', async (event, shardId) => {
    console.warn(`Shard ${shardId} lost connection. Code: ${event.code}`);
    
    if (reconnectAttempts >= maxAttempts) {
      console.error(`Maximum reconnection attempts (${maxAttempts}) reached. Exiting process for supervisor restart.`);
      process.exit(1);
    }

    reconnectAttempts++;
    const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000);
    console.log(`Attempting reconnect #${reconnectAttempts} in ${delay}ms...`);
    
    setTimeout(async () => {
      try {
        await client.login(process.env.DISCORD_TOKEN);
      } catch (error) {
        console.error(`Reconnection failed: ${error.message}`);
      }
    }, delay);
  });

  return client;
}

module.exports = { createBotClient };

Test your setup by executing your entry point:

node index.js

If This Doesn't Work: The 3 Deepest Failure Points

Even after configuring intents, optimizing memory limits, and writing custom reconnection loops, stubborn disconnects can persist due to infrastructure edge cases.

1. Cloudflare or Enterprise Firewall Interference

If you are hosting your bot on an enterprise network, a corporate server with strict outbound firewall rules, or behind a restrictive VPN, outbound WebSocket connections to Discord's gateway domains (gateway.discord.gg) may be aggressively throttled or terminated after a specific duration threshold. Verify outbound WebSocket traffic is whitelisted on port 443 and that your server isn't routing traffic through a high-latency proxy.

2. DNS Resolution Failures on Container Restarts

When a containerized bot (Docker/Kubernetes) loses connection and attempts to resolve gateway.discord.gg during a network blip, intermittent DNS failures can prevent reconnection. If your container's /etc/resolv.conf points to an unreliable internal DNS resolver, your bot will throw ENOTFOUND errors and remain offline. Fix: Explicitly configure reliable public DNS servers like Cloudflare (1.1.1.1) or Google (8.8.8.8) inside your Dockerfile or Kubernetes deployment manifest.

3. Rate Limiting via Rapid Re-authentication

If your code includes an aggressive try/catch loop that calls client.login() every single time a disconnect event fires without a delay, Discord's API gateway will flag your IP address for abuse and temporarily ban your token from connecting (Global Rate Limit). Ensure you always use exponential backoff as shown in the reconnection manager above, and never hammer the login endpoint.

Prevention and Moving to Sharding

As your bot scales past 2,000 servers (guilds), single-process gateway connections become inherently unstable because a single node cannot efficiently process the payload volume of multiple large communities.

When you cross that threshold, stop using a standard Client instance and implement ShardingManager. This splits your bot's gateway connection across multiple isolated Node.js processes, ensuring that if one shard experiences a network drop or disconnects, your other shards remain fully online and responsive to users.

Create a file named cluster.js:

const { ShardingManager } = require('discord.js');
const path = require('path');

const manager = new ShardingManager(path.join(__dirname, 'index.js'), {
  token: process.env.DISCORD_TOKEN,
  totalShards: 'auto', // Automatically calculates optimal shard count based on guild size
});

manager.on('shardCreate', shard => {
  console.log(`Launched shard ${shard.id}`);
});

manager.spawn().catch(err => {
  console.error('Error spawning shards:', err);
});

Run your sharded cluster with Node:

node cluster.js

By isolating your gateway connections, monitoring close codes, and maintaining clean heartbeat cycles, you eliminate the underlying network instability and ensure your bot stays online indefinitely.