Corey Corey
GuidesHow it worksWhat is insidePricingDocsStart with Corey

Run Corey on a Hetzner VPS

Run Corey around the clock by provisioning a Hetzner VPS, hardening SSH, installing Claude Code, loading secrets through Doppler, and scheduling work with cron. Corey is not a resident daemon - the server is what stays on, and cron wakes Corey to do the work, so scheduled jobs run whether or not your laptop is open.

Running Corey on a dedicated server means it works around the clock, runs scheduled tasks while you sleep, and is not tied to your laptop being open.

Every setting below is what we run on our own box, which has been live since May 2026. Where a choice genuinely depends on you rather than on us, this guide says so and gives you the trade-off instead of a false recommendation.

Done-for-you setup

Don’t want to run the server yourself? We can provision, configure and manage the VPS for you so Corey is ready out of the box. Get in touch at kristian@pressonetwork.com.

Step 1: Provision a VPS on Hetzner

Hetzner Cloud is a good choice for running Corey: straightforward pricing, solid EU-based infrastructure, and a clean API if you want to automate provisioning later.

  1. Create a Hetzner Cloud account if you do not have one.
  2. Create a new project for your Corey server.
  3. Click Add Server.
  4. Type: CPX32 - 4 vCPU (AMD), 8 GB RAM, 160 GB SSD. This is what we run. It costs roughly £14/month including weekly backups.
  5. Image: Ubuntu 26.04 LTS.
  6. Location: pick the region closest to you. We run in Falkenstein (FSN1) because we are UK-based and the whole EU set is within a few milliseconds; if you are outside Europe, Hetzner’s US regions will be a better SSH experience. If you have a data-residency obligation, that decides it for you and latency does not get a vote.
  7. Add your SSH public key so you can connect securely. Do this during creation rather than afterwards - it saves a password-login round trip.
  8. Click Create and Buy. Note the server’s IP address once it is running.

On sizing. A smaller box will run Corey, but agent sessions are memory-hungry and RAM is the constraint that bites first, not CPU. The 8 GB tier is where we stopped having problems.

Step 2: Harden the server

Do this before you put any credentials on the box. It takes five minutes.

Connect as root using the key you added:

shell
ssh root@<your-server-ip>

Create a user for yourself, give it sudo, and copy your key across:

shell
adduser --disabled-password --gecos "" corey
usermod -aG sudo corey
rsync --archive --chown=corey:corey ~/.ssh /home/corey/

Now lock down SSH. Confirm you can open a second terminal and ssh corey@<your-server-ip> successfully before you close this one - locking yourself out of a fresh server is recoverable only through the Hetzner web console.

shell
# /etc/ssh/sshd_config
PermitRootLogin no
PasswordAuthentication no
shell
systemctl restart ssh

Enable the firewall and install fail2ban:

shell
ufw default deny incoming
ufw allow 22
ufw --force enable
apt install -y fail2ban

Add swap. This one is not optional. Our box ran with zero swap until August 2026, and the symptom was agents dying mid-job with no error in any log - the OOM killer terminates the largest process outright rather than degrading. Two gigabytes is enough to turn a silent kill into a slow patch.

shell
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
free -h   # confirm the Swap row is no longer zero

Claude Code needs a paid Claude plan to sign in. If you do not have one yet, our referral link gets you a free week of Claude Code.

Step 3: Install dependencies and Claude Code

As your new user, update the system:

shell
sudo apt update && sudo apt upgrade -y

Install Node.js 22 LTS:

shell
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs
node --version   # should print v22.x.x

Install Claude Code:

shell
npm install -g @anthropic-ai/claude-code
claude --version

Authenticate Claude Code with your Claude subscription by running claude once interactively and following the browser prompt. Use the subscription login rather than an API key: an ANTHROPIC_API_KEY in the environment overrides the subscription token and silently routes everything to metered API billing.

Step 4: Install Doppler and load secrets

Corey reads its integration credentials from environment variables. On a server, Doppler is the right way to hold them - secrets live in Doppler and are injected at runtime, never written into shell scripts or .env files on disk.

Install the Doppler CLI:

shell
sudo apt-get install -y apt-transport-https
curl -sLf --retry 3 --tlsv1.2 --proto "=https" \
  'https://packages.doppler.com/public/cli/gpg.DE2A7741A397C129.key' \
  | sudo gpg --dearmor -o /usr/share/keyrings/doppler-archive-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/doppler-archive-keyring.gpg] \
  https://packages.doppler.com/public/cli/deb/debian any-version main" \
  | sudo tee /etc/apt/sources.list.d/doppler-cli.list

sudo apt-get update && sudo apt-get install doppler

Authenticate and select a scope:

shell
doppler login
doppler setup

The project and config are yours, not ours - doppler setup walks you through picking them interactively. There is no Corey-owned project you connect to.

Pick the config deliberately, and write down which one you picked. A Doppler project usually carries several configs (dev, stg, prd), and they hold different secrets. Adding a key to one config and pointing the box at another produces the worst kind of failure: the credential exists, the dashboard shows it, and the box cannot see it. We lost a day to exactly that in August 2026. Confirm which scope the box actually resolved:

shell
doppler run -- bash -c 'echo $DOPPLER_PROJECT/$DOPPLER_CONFIG'

There is no fixed list of keys Corey requires. You need none to start. Each key you add corresponds to a tool you have connected - a GitHub token, a Stripe restricted key, an analytics key - and Corey only needs the ones for integrations you actually use.

To confirm the box can see your secrets without ever printing a value:

shell
doppler secrets --only-names

Never run bare doppler secrets, and never doppler configure. Both print live credential values to your terminal, and anything printed to a terminal should be treated as burned and rotated.

Step 5: Schedule Corey with cron

Corey is not a resident daemon, and there is no corey.service to install. The server is what stays on. Cron wakes Corey to do a piece of work, Corey finishes it and exits. That is deliberate: an agent process idling for six days between Monday reports is holding memory for nothing, and a crashed daemon is a silent outage, whereas a failed cron run leaves a log line and runs again on schedule.

Edit your crontab:

shell
crontab -e
shell
# Cron runs a NON-LOGIN shell, so neither doppler nor claude is on PATH by
# default and every job fails with "command not found". Set it explicitly.
PATH=/home/corey/.local/bin:/usr/local/bin:/usr/bin:/bin

# Weekly report, Mondays at 08:00
0 8 * * 1  cd ~/corey && doppler run -- claude -p "Run the weekly report" >> ~/corey/logs/weekly.log 2>&1

# Backlog triage every two hours on weekdays, 08:00-20:00
0 8-20/2 * * 1-5  cd ~/corey && doppler run -- claude -p "Triage anything new and flag what needs me" >> ~/corey/logs/triage.log 2>&1

The same PATH trap catches non-interactive SSH, which is also a non-login shell. If ssh yourbox "doppler run -- ..." fails while the identical command works once you are logged in, this is why:

shell
export PATH="$HOME/.local/bin:$PATH"

Use claude -p for scheduled work. It runs the prompt non-interactively and exits, which is what you want from cron; plain claude opens a session and will hang forever waiting for input that cron will never give it.

For interactive work, use tmux rather than a service. Start a long-lived session you can reattach to from a laptop or a phone:

shell
tmux new -s corey     # start
# ctrl-b then d to detach
tmux attach -t corey  # come back to it later, from anywhere

What this guide does not cover

Two things are deliberately absent rather than forgotten.

A provisioning script. There is no cloud-init template or Ansible playbook yet. Every step above is manual on purpose, because you should be able to read what is being done to a server that will hold your credentials.

A price for the managed setup. The done-for-you option above is real, but it is scoped per customer rather than priced off a list. Email and we will quote it.

Related