Provider clusterFresh-image operationsUpdated 2026-11-05

RackNerd ctrl setup: from a fresh VPS to a secured first app

Day-zero operations: preserve recovery, verify access, control exposure, and learn what ctrl installed before you change it.

Workload before headline price — verify the plan, terms and recovery path
How to use this. Steps reflect common operator problem-handling patterns and public RackNerd/ctrl flows. Follow your live panel labels; discover actual OS, services, and paths on your host before production changes.

Day-zero sequence and recovery boundary

Use this runbook only after choosing the exact SKU and region in the RackNerd review. You need the ctrl account, the public IPv4 from the order handoff, an SSH client, and either a brand-new VPS or an off-server restore you have already proved. Root is needed for initial OS work; subsequent commands use sudo.

RackNerd says a ctrl one-click application reinstalls Ubuntu and permanently erases existing data. Do not treat it as a package installer. For an occupied server, stop at the erase gate below and follow the checked backup and isolated restore runbook first. Provider snapshot availability was not established publicly, so no step assumes one.

Reserve a maintenance window, keep ctrl/VNC open, and save the following non-secret facts locally: exact product/SKU, selected region, OS/app image, public IP, SSH port, installation time, and the official credential-delivery location. Never paste the root password, private SSH key, .env, application API key, or n8n encryption key into a ticket or shared command transcript.

Install from ctrl without destroying the wrong system

  1. Sign in to ctrl through RackNerd’s official client handoff. Confirm the VPS hostname/IP matches the intended service—not merely a similar name.
  2. Open the one-click Applications area. Select the intended app only after checking its published RAM/vCPU floor and disk budget. n8n lists 2 vCPU/2 GB; Ollama and several connected AI apps list 2 vCPU/4 GB; OpenClaw lists 4 vCPU/8 GB.
  3. Read the destructive warning. The following gate must all be true:
[ ] VPS identity and public IP match the intended empty target.
[ ] Every existing application, database and credential is expendable,
    OR an off-server export and isolated restore have both succeeded.
[ ] The selected app, OS and resource tier match the sizing decision.
[ ] ctrl/VNC access works independently of the current SSH daemon.
[ ] I understand this reinstalls Ubuntu and permanently erases the disk.
[ ] I will retrieve credentials from ctrl; I will not post them publicly.
  1. If any gate fails, cancel. Reinstallation is the recovery only when erasure is intentional. RackNerd advertises roughly 15–45 minutes for app installation; this is not an AiXVPS live-install result. Poll ctrl rather than repeatedly launching installs.
  2. After completion, record the non-secret app name/version if displayed and the credential location. Test the HTML5 VNC console now, before hardening SSH. If VNC fails, resolve provider access before disabling any recovery route.
  3. Open the application only through its discovered listener. Do not infer that an installer created HTTPS, a public hostname, backups, or safe remote model access.

Failure handling: if installation exceeds the advertised window or the service never appears, capture the time, selected app and error without secrets; use ctrl resource/VNC views and contact RackNerd infrastructure support. Application configuration remains your responsibility on an unmanaged VPS. Do not rerun the installer over newly created data.

Verify the host before trusting SSH

From VNC, inspect the host-key fingerprint and listening SSH service as root:

ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
systemctl is-active ssh
ss -lntp '( sport = :22 )'

On your workstation, replace the documentation IP and key paths. Use a unique key filename; the guard prevents accidental overwrite:

install -m 700 -d "$HOME/.ssh"
KEY="$HOME/.ssh/racknerd_ed25519"
test ! -e "$KEY" && test ! -e "$KEY.pub" || {
  printf '%s\n' 'Refusing to overwrite an existing key; choose another KEY.' >&2
  exit 1
}
ssh-keygen -t ed25519 -a 100 -f "$KEY" -C 'racknerd-admin'
VPS_IP='203.0.113.10'                 # replace: actual VPS public IPv4
ssh-keyscan -T 5 "$VPS_IP" > /tmp/racknerd-hostkeys
ssh-keygen -lf /tmp/racknerd-hostkeys
rm -f /tmp/racknerd-hostkeys

Compare the ED25519 fingerprint through the independent VNC path. A network scan alone does not authenticate the host. If it differs, stop; do not bypass host-key checking. After a deliberate reinstall, remove only that host’s obsolete known_hosts entry and verify the new fingerprint.

Connect initially with the credential delivered by ctrl:

ssh -i "$KEY" -o IdentitiesOnly=yes "root@$VPS_IP"

This works only if the public key was installed during provisioning or through VNC. Otherwise authenticate using the provider’s documented initial method, then install your public key in the next section. If connection fails, distinguish DNS/routing, port/filter, host-key and authentication errors; do not “fix” all of them by disabling the firewall.

Create a sudo administrator and lock SSH deliberately

Run as root in the verified session. Replace deploy only if you consistently replace every later occurrence. The public key is safe to copy; the private key never leaves your workstation.

id deploy >/dev/null 2>&1 || useradd --create-home --shell /bin/bash --groups sudo deploy
install -m 700 -d -o deploy -g deploy /home/deploy/.ssh
# Paste the single line from racknerd_ed25519.pub between the quotes.
ADMIN_PUBLIC_KEY='ssh-ed25519 AAAA_REPLACE_WITH_YOUR_PUBLIC_KEY racknerd-admin'
printf '%s\n' "$ADMIN_PUBLIC_KEY" > /home/deploy/.ssh/authorized_keys
chown -R deploy:deploy /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys
passwd -l deploy
sshd -t

Open a second terminal and prove both login and privilege before changing root access:

ssh -i "$KEY" -o IdentitiesOnly=yes "deploy@$VPS_IP"
sudo -v
sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication|pubkeyauthentication) '

Keep the original root/VNC recovery session open. If the second login or sudo -v fails, repair group membership, ownership, modes and key selection first. Only then add a drop-in to disable password and root SSH login. Confirm your chosen OS supports /etc/ssh/sshd_config.d/*.conf via sshd -T; do not blindly overwrite distribution configuration.

sudo install -m 644 /dev/null /etc/ssh/sshd_config.d/60-aixvps-access.conf
sudo tee /etc/ssh/sshd_config.d/60-aixvps-access.conf >/dev/null <<'EOF'
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no
EOF
sudo sshd -t && sudo systemctl reload ssh

Verify again in a new terminal. Rollback: from the still-open root/VNC session, remove only 60-aixvps-access.conf, run sshd -t, and reload ssh. If no recovery session exists, do not make this change.

Apply updates and build a two-layer firewall

Patch while VNC remains available:

sudo apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get -y upgrade
sudo systemctl --failed --no-pager
if sudo test -f /var/run/reboot-required; then
  printf '%s\n' 'Reboot required; confirm VNC, then schedule: sudo systemctl reboot'
fi

A reboot is a deliberate step, not an automatic script action. Verify the console and all discovered services afterward.

At the OS layer, permit SSH before enabling UFW. Keep 5678 (n8n), 11434 (Ollama), 9119 (Hermes dashboard), 3000 and arbitrary container ports private unless an explicit architecture requires otherwise. Public HTTP/HTTPS is optional until a reverse proxy and DNS are configured.

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
# Optional, only for a deliberate public reverse proxy:
# sudo ufw allow 80/tcp
# sudo ufw allow 443/tcp
sudo ufw --force enable
sudo ufw status verbose
sudo ss -lntup

In ctrl, configure the optional provider-side inbound firewall to permit the same intentional flows. Start with SSH; narrow its source to your stable administrator CIDR only if you have another recovery route. A dynamic home IP can lock you out. Test a new SSH connection after each layer, not after changing both blindly.

If unreachable, use VNC to inspect sudo ufw status numbered and the SSH listener. Disable UFW temporarily only while a safe console session is active, restore the intended SSH rule, then re-enable and retest. For the ctrl layer, remove only the rule that caused the block. Never solve exposure by opening every port.

Docker-published ports require a separate exposure test because packet-filter behavior is not a simple list of host daemon sockets. Discover mappings without dumping environment variables:

if command -v docker >/dev/null 2>&1; then
  sudo docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}'
  sudo docker compose ls
fi

A bound port such as 0.0.0.0:11434 is not made safe by UFW intent alone. Rebind the service to loopback or a private interface, recreate it through its actual configuration, and retest from inside and outside. Preserve the previous compose/config file for rollback.

Inventory the delivered system without leaking secrets

The same “n8n” app can leave different systemd units, container names, directories or versions as images evolve. Build a redacted inventory before editing. This read-only block needs sudo for socket and container metadata; it prints no configuration or credential contents.

printf '%s\n' '== OS / kernel ==' 
cat /etc/os-release
uname -r
printf '%s\n' '== Capacity =='
free -h
lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINTS
df -hT -x tmpfs -x devtmpfs
printf '%s\n' '== Listeners =='
sudo ss -lntup
printf '%s\n' '== Running services =='
systemctl list-units --type=service --state=running --no-pager
printf '%s\n' '== Candidate deployment files (names only) =='
sudo find /opt /srv /root -maxdepth 3 -type f \
  \( -name 'compose.yml' -o -name 'compose.yaml' \
     -o -name 'docker-compose.yml' -o -name 'docker-compose.yaml' \) \
  -print 2>/dev/null
if command -v docker >/dev/null 2>&1; then
  printf '%s\n' '== Containers / Compose projects =='
  sudo docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}'
  sudo docker compose ls
fi

Save this output in a private local inventory with restricted permissions, redacting public identifiers before sharing. Do not run env, printenv, docker inspect wholesale, cat .env, cat config, or database dumps into the terminal. Metadata commands can still reveal container names and paths; treat the report as confidential.

Verification: the inventory identifies an actual service manager, deployment path, exposed ports, free memory and free disk. If it identifies none, return to ctrl/VNC and the installer status rather than creating a parallel installation.

First application smoke test and stop conditions

Use only a listener found in your inventory. The example is n8n on its conventional loopback port; replace it if your actual deployment differs:

curl --fail --silent --show-error --max-time 10 http://127.0.0.1:5678/healthz

Success proves local HTTP reachability only. /healthz/readiness, database persistence, encryption material and execution retention belong to day-two n8n operations. For AI apps, follow the bounded model and private-endpoint checks rather than exposing their admin or inference ports.

Stop if free disk approaches your export requirement, memory pressure causes service failures, an unexpected public listener appears, or the deployment cannot be tied to a known unit/Compose project. Preserve logs through the discovered mechanism (journalctl -u UNIT or docker logs CONTAINER) and redact secrets before sharing.

The day-zero acceptance record is simple: host fingerprint verified, non-root sudo login works, both firewall layers are intentional, no unnecessary public port exists, OS updates are complete, and the actual app topology is known. Continue to n8n operations or establish the canonical backup/export drill before creating valuable data.