--- title: "Running RCON Commands from Discord" excerpt: "Send RCON to your CS 1.6 server from a Discord channel: how GoldSrc RCON's challenge-response works over UDP, a minimal bot pattern, and the access controls that keep it from becoming a backdoor." category: "administration" tags: "discord, rcon" image: "" read_time: "9 min read" ---

Running RCON from Discord means your admins can kick, change the map, or read status from their phone without opening a game client. It is genuinely useful — and genuinely dangerous, because you are exposing the most powerful control channel your server has to a chat app. Done carefully it is fine; done carelessly it hands your rcon_password to anyone who compromises the bot. This explains how GoldSrc RCON actually works and how to bridge it safely.

1. Understand GoldSrc RCON (it is UDP, not Source RCON)

CS 1.6 does not use the TCP Source RCON protocol that newer Source games use. Its RCON runs over the same UDP game port (default 27015) and is challenge-based. The exchange is:

  1. The client sends a challenge request to the server.
  2. The server replies with a numeric challenge token.
  3. The client sends the actual command with the challenge and the password.

The raw packets look like this (each prefixed with four 0xFF bytes):

--> \xFF\xFF\xFF\xFF getchallenge rcon\n
<-- \xFF\xFF\xFF\xFF challenge rcon 1234567890\n
--> \xFF\xFF\xFF\xFF rcon 1234567890 "your_rcon_password" status\n
<-- \xFF\xFF\xFF\xFF ...command output...

The password travels in cleartext inside the UDP payload, so anyone who can sniff the path can read it. That single fact drives every security decision below.

2. The bot architecture

You need a small always-on process that: listens to a Discord channel, authenticates the sender, translates an allowed message into an RCON command, sends it via the challenge-response above, and posts the reply back. Keep the bot on the same host or network as the game server so the cleartext RCON never crosses the public internet.

Discord channel  -->  bot (Discord API)  -->  UDP RCON  -->  CS 1.6 server
                                          (localhost / LAN)

3. A minimal RCON sender

The core is a few lines of socket code. This Python function does the two-step challenge and returns the output — the exact same logic any language uses:

import socket

def rcon(host, port, password, command, timeout=3):
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.settimeout(timeout)
    s.connect((host, port))
    # 1) ask for a challenge
    s.send(b"\xff\xff\xff\xff" + b"getchallenge rcon\n")
    reply = s.recv(1400).decode("latin-1", "ignore")
    token = reply.split("challenge rcon ")[1].split()[0]
    # 2) send the command with challenge + password
    packet = b"\xff\xff\xff\xff" + (
        'rcon %s "%s" %s\n' % (token, password, command)
    ).encode("latin-1")
    s.send(packet)
    out = s.recv(4096).decode("latin-1", "ignore")
    s.close()
    return out.lstrip("\xff").lstrip("l")   # strip packet header

Wire that into whatever Discord library you prefer (discord.py, discord.js) so an allowed message calls rcon(...) and posts the return value. The Discord half is boilerplate; the security half is not.

4. Lock it down — this is the whole point

An unrestricted Discord-to-RCON bridge is a remote root shell for your server. Apply all of these:

5. Rotate the password if it ever leaks

Because RCON is cleartext and now also lives in a bot config, treat the password as exposed the moment anything goes wrong — a leaked config, a kicked admin, a suspicious log entry. Change rcon_password in server.cfg, reload, and update the bot. A long random password is assumed; see the RCON notes in the essential cvars list.

Common errors

Verification

Test the sender from a shell on the bot host before touching Discord:

python3 -c "import bot; print(bot.rcon('127.0.0.1',27015,'PW','status'))"

A correct setup prints the status block with hostname, map and player list. Then, from Discord, send an allowed command as an authorized user and confirm the reply posts; send a forbidden command and confirm it is rejected and logged. Finally, send from an unauthorized account and confirm the bot ignores it entirely — that silent rejection is the test that matters most.