How to Install Carbon on a Linux Rust Server
Install Carbon on a Linux Rust server run by systemd: the release archive, the doorstop LD_PRELOAD, unit edit, checking it loaded, updates and uninstall.
Last updated Verified on Ubuntu 26.04.1 LTS, Rust build 25581777, Carbon 2.0.259, 2026-09-28
On this page
Carbon is a mod loader for Rust that runs Oxide plugins. On Windows you unpack it and start the server. On Linux under systemd there is one extra step that trips people up: the preloader has to be injected into the process through an environment variable, and systemd does not give a service the environment you have in your shell. This guide covers a server that runs as a systemd service on your own Linux machine. The paths match what Panelra uses (/opt/rust-servers/instance-1); swap in your own install directory and unit name.
Before you start
- Rust is already installed with SteamCMD and starts from a systemd unit.
- You have root on the machine.
- The Rust server is up to date. Carbon's own install docs put "verify and update game files" before anything else, and the production Carbon build expects the current Rust build.
- You have a backup of the install directory if the server already has players. The unit edit and a later uninstall both change files in it.
Set two shell variables so the commands below can be pasted as they are:
RUST_DIR=/opt/rust-servers/instance-1
UNIT=/etc/systemd/system/rust-server.service
Download the Linux release
Carbon is published on GitHub under the production_build tag. Each release has Linux and Windows archives in two flavors, Release and Minimal, plus a small .info file with the version. Stop the server first so no files are in use, then download and extract on top of the Rust folder:
systemctl stop rust-server
cd "$RUST_DIR"
curl -fL -o carbon.tar.gz \
https://github.com/CarbonCommunity/Carbon/releases/download/production_build/Carbon.Linux.Release.tar.gz
tar -xzf carbon.tar.gz -C "$RUST_DIR"
rm carbon.tar.gz
To see which version you got before you extract anything, read the matching info file:
curl -fsL https://github.com/CarbonCommunity/Carbon/releases/download/production_build/Carbon.Linux.Release.info
On 2026-09-28 it reported "Version": "2.0.259".
What the archive contains
The archive has four entries at its root. Knowing them matters later, because they are exactly what you remove to uninstall:
| Path | What it is |
|---|---|
carbon/ | Carbon itself: managed/ (the DLLs, including Carbon.Preloader.dll), native/, tools/environment.sh, and empty plugins/, configs/, data/ and extensions/ folders |
carbon.sh | A launcher script that sources environment.sh and then runs RustDedicated with your arguments |
libdoorstop.so | Unity Doorstop, the native library that gets Carbon's preloader into the game process |
Carbon.targets | An MSBuild file for plugin developers. The server does not need it at run time |
Two small fixes before the first boot. The archive does not ship carbon/logs, and Carbon's bootstrap writes carbon/logs/Carbon.Bootstrap.log on first start; without the folder the first boot fails with a DirectoryNotFoundException. And carbon.sh must be executable:
mkdir -p "$RUST_DIR/carbon/logs"
chmod +x "$RUST_DIR/carbon.sh"
Why systemd needs LD_PRELOAD
This is the whole trick. Here is carbon.sh from the release, without its copyright header:
SCRIPT=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "${SCRIPT}/carbon/tools/environment.sh"
"${SCRIPT}/RustDedicated" "$@"
And the part of carbon/tools/environment.sh that matters:
export TERM=xterm
export DOORSTOP_ENABLED=1
export DOORSTOP_TARGET_ASSEMBLY="${CARBONENV_BASEDIR}/carbon/managed/Carbon.Preloader.dll"
export LD_PRELOAD="${CARBONENV_BASEDIR}/libdoorstop.so"
export LD_LIBRARY_PATH="${CARBONENV_BASEDIR}:${CARBONENV_BASEDIR}/RustDedicated_Data/Plugins/x86_64"
LD_PRELOAD makes the dynamic linker load libdoorstop.so into RustDedicated before anything else. Doorstop then runs Carbon.Preloader.dll inside the game's managed runtime, and the preloader loads the rest of Carbon. Nothing in the Rust server reads the carbon/ folder on its own. If the preload is missing, Rust boots as vanilla and says nothing about Carbon.
systemd starts a service with a clean environment. It does not read /etc/profile, .bashrc or anything you exported in your SSH session, so running the server by hand from a shell where you sourced environment.sh proves nothing about the service. The variables have to come from the unit itself: either ExecStart goes through carbon.sh, or the unit sets them with Environment=.
Change the systemd unit
A typical vanilla unit starts RustDedicated directly. This is the shape Panelra writes, trimmed:
[Service]
Type=simple
User=root
WorkingDirectory=/opt/rust-servers/instance-1
EnvironmentFile=/opt/rust-servers/instance-1/.env
ExecStart=/opt/rust-servers/instance-1/RustDedicated \
-batchmode \
-nographics \
+server.identity my-server \
+server.port 28015 \
+server.queryport 28017 \
+app.port 28082 \
+rcon.port 28016 \
+rcon.web 1 \
+rcon.password ${RCON_PASSWORD}
Restart=always
RestartSec=5
Option 1: launch through carbon.sh (recommended)
Replace the program path in ExecStart and keep every argument. This is the only change Panelra's agent makes to the unit when it installs Carbon:
sed -i "s#$RUST_DIR/RustDedicated#$RUST_DIR/carbon.sh#" "$UNIT"
grep -n 'ExecStart=' "$UNIT"
The line should now read ExecStart=/opt/rust-servers/instance-1/carbon.sh \. The advantage is that carbon.sh always sources the environment.sh that matches the Carbon build on disk.
Option 2: set the variables in the unit
If you want ExecStart to keep pointing at RustDedicated, copy the variables from environment.sh into the [Service] section:
Environment=LD_PRELOAD=/opt/rust-servers/instance-1/libdoorstop.so DOORSTOP_ENABLED=1 TERM=xterm
Environment="DOORSTOP_TARGET_ASSEMBLY=/opt/rust-servers/instance-1/carbon/managed/Carbon.Preloader.dll"
Environment="LD_LIBRARY_PATH=/opt/rust-servers/instance-1:/opt/rust-servers/instance-1/RustDedicated_Data/Plugins/x86_64"
Use absolute paths; systemd does not expand ${CARBONENV_BASEDIR}. If a future Carbon release changes environment.sh, you have to copy the change by hand, which is why option 1 is simpler.
Either way, reload systemd and start the server:
systemctl daemon-reload
systemctl start rust-server
Check that Carbon loaded
Do not trust "the server started". Check three things.
1. The journal has no launcher error. If carbon.sh cannot find environment.sh, bash prints an error and carries on, and the server boots vanilla. This is a real line from our test host, where carbon/tools had gone missing:
journalctl -u rust-server --since "15 min ago" --no-pager | grep environment.sh
/opt/rust-servers/instance-1/carbon.sh: line 9: /opt/rust-servers/instance-1/carbon/tools/environment.sh: No such file or directory
No output from the grep is what you want. The --since keeps old errors from before your fix out of the result.
2. The game process actually has the preload. With carbon.sh, systemctl status shows bash .../carbon.sh as the main PID and RustDedicated as its child, because the script does not exec. Look at the child:
PID=$(pgrep -f "$RUST_DIR/RustDedicated" | head -1)
tr '\0' '\n' < /proc/$PID/environ | grep -E '^(LD_PRELOAD|DOORSTOP)'
grep -c libdoorstop /proc/$PID/maps
A working install prints the LD_PRELOAD and DOORSTOP_ variables and a count above 0. Empty output and 0 mean Carbon is not in the process.
3. Carbon answers over RCON. Once the server is up, run c.version from any RCON client. A vanilla server replies with an unknown command message. Panelra uses the same command to decide whether a framework is active. You should also see carbon/logs/Carbon.Bootstrap.log appear after the first boot.
Plugins go in $RUST_DIR/carbon/plugins, and their configs are written to carbon/configs (plural, unlike Oxide's oxide/config).
Updating Carbon
Carbon updates itself. On startup it compares the installed build with the current one, downloads the patch for the same build type and OS from GitHub, and unpacks it into the Carbon folder, mainly carbon/managed. On the production build it only moves to a new protocol version, for example on a wipe day, once you have updated Rust itself. So the routine is:
- Stop the server.
- Update Rust with SteamCMD.
- Start the server and let Carbon update during boot.
If you turned self-updating off ("Enabled": false under "SelfUpdating" in carbon/config.json), update by stopping the server and repeating the download and extract steps above. Extracting over the top replaces Carbon's files and leaves carbon/plugins, carbon/configs and carbon/data alone, because the archive ships those folders empty. Re-run the chmod and check that ExecStart still points at carbon.sh.
Uninstalling Carbon safely
Order matters. If you delete the files while the unit still launches carbon.sh, systemd will keep trying to start a script that no longer exists, and with Restart=always it will do that every few seconds. If you delete libdoorstop.so but leave it in an Environment=LD_PRELOAD line, every start logs a preload error from the dynamic linker.
carbon/ holds your plugins, their configs and their data. Copy it somewhere first if you might come back:
systemctl stop rust-server
cp -a "$RUST_DIR/carbon" /root/carbon-backup
Point ExecStart back at RustDedicated and drop any doorstop variables you added with option 2. The arguments stay as they are:
sed -i "s#$RUST_DIR/carbon.sh#$RUST_DIR/RustDedicated#" "$UNIT"
grep -nE 'LD_PRELOAD|DOORSTOP' "$UNIT"
If the grep prints anything, remove those assignments by hand. Then remove exactly the files the archive installed, plus doorstop_config.ini if an older manual install left one:
cd "$RUST_DIR"
rm -f carbon.sh libdoorstop.so doorstop_config.ini Carbon.targets
rm -rf carbon
systemctl daemon-reload
systemctl start rust-server
Do not delete HarmonyMods/. The Carbon archive does not ship it, the Rust server uses it for Harmony mods, and anything you put there yourself lives there. Do not delete RustDedicated_Data/ or steamapps/ either; those belong to the game.
After the start, run the checks from "Check that Carbon loaded" in reverse: no LD_PRELOAD on the process, and c.version is an unknown command.
Let Panelra handle it
Panelra does all of this for you: install the agent on your Linux host with one command, then pick Carbon in the dashboard. It downloads the release, creates carbon/logs, switches the unit to carbon.sh, and tells you to restart to activate it. Framework updates stop the server, update Carbon and start the server again if it was running. Uninstall stops the server, removes the Carbon files, restores the vanilla ExecStart, keeps HarmonyMods and starts the server again if it was running. If a unit launches through carbon.sh while environment.sh is missing, Panelra flags that the server is running without Carbon instead of reporting Carbon as installed.
Frequently asked questions
Why does my server start fine but Carbon never loads?
Can I put LD_PRELOAD in /etc/environment or my shell profile instead?
Do I need to reinstall Carbon after every Rust update?
Should I use the Release or the Minimal archive?
Skip the manual work: install the Panelra agent
Wipes, updates, restarts, plugins and crash alerts for your Rust servers, from one dashboard. One install command on your Linux host, no inbound ports for the agent.
Free during the open beta. Pricing will be announced before the beta ends.