# Remote Dev Server Guide Part 2: Initial Server Setup Date: 2026-03-06 · 6 min read Tags: hetzner, ubuntu, devops, server URL: https://ahmedelywa.com/blog/hetzner-remote-dev-part-2-initial-server-setup --- In [Part 1](/blog/hetzner-remote-dev-part-1-choosing-your-server), we picked and ordered a Hetzner dedicated server. Now we have a fresh Ubuntu 24.04 box with root access. Let's turn it into a development machine. ## Create Your Dev User Never develop as root. Create a dedicated user with sudo privileges: ```bash adduser dev usermod -aG sudo dev ``` Set up SSH key authentication for the new user. This is a prerequisite — in [Part 3](/blog/hetzner-remote-dev-part-3-security-with-tailscale) we'll disable password authentication entirely, so keys must work first: ```bash # On your local machine, copy your public key ssh-copy-id dev@YOUR_SERVER_IP # Or manually mkdir -p /home/dev/.ssh echo "your-public-key-here" >> /home/dev/.ssh/authorized_keys chmod 700 /home/dev/.ssh chmod 600 /home/dev/.ssh/authorized_keys chown -R dev:dev /home/dev/.ssh ``` **Verify key login works** before moving on — open a new terminal and confirm `ssh dev@YOUR_SERVER_IP` connects without asking for a password. Switch to the dev user for the rest of this guide: ```bash su - dev ``` ## System Updates ```bash sudo apt update && sudo apt upgrade -y ``` Install essential build tools: ```bash sudo apt install -y build-essential curl git wget unzip htop tmux jq lsof ``` ## Install Node.js I use the [NodeSource](https://github.com/nodesource/distributions) APT repository for Node.js. It installs system-wide, so all users (including the `deploy` user) get access without extra setup: ```bash curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash - sudo apt install -y nodejs ``` Verify: ```bash node --version # v24.x.x npm --version ``` ## Install Bun [Bun](https://bun.sh) is a fast JavaScript runtime and package manager. I use it for building and running Next.js apps: ```bash curl -fsSL https://bun.sh/install | bash source ~/.bashrc bun --version ``` Bun installs to `~/.bun/bin/`. It's significantly faster than npm/yarn for installing dependencies and running builds. ## Install Docker Docker is essential for running databases and services in isolation: ```bash # Add Docker's official GPG key and repository sudo apt install -y ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin # Add your user to the docker group (no sudo needed for docker commands) sudo usermod -aG docker dev newgrp docker ``` ### Database Containers I run PostgreSQL and Redis in Docker containers for each project. Here's an example `docker-compose.yml`: ```yaml services: postgres: image: postgres:18 restart: always environment: POSTGRES_USER: dev POSTGRES_PASSWORD: dev POSTGRES_DB: myapp ports: - "127.0.0.1:5432:5432" volumes: - pgdata:/var/lib/postgresql/data redis: image: redis:alpine restart: always ports: - "127.0.0.1:6379:6379" volumes: pgdata: ``` Key detail: bind to `127.0.0.1` so databases are only accessible locally, not from the internet. ## Install PostgreSQL Client You'll want the `psql` client for connecting to your database containers: ```bash sudo apt install -y postgresql-client-16 ``` ## Set Up tmux tmux keeps your sessions alive when you disconnect. This is critical for a remote dev server — you can close your laptop, come back later, and everything is exactly where you left it. Create a basic `.tmux.conf`: ```bash cat >> ~/.tmux.conf << 'EOF' # Better prefix set -g prefix C-a unbind C-b bind C-a send-prefix # Mouse support set -g mouse on # Start window numbering at 1 set -g base-index 1 setw -g pane-base-index 1 # Increase scrollback set -g history-limit 50000 # Better colors set -g default-terminal "screen-256color" set -ga terminal-overrides ",xterm-256color:Tc" EOF ``` Start a named session for your work: ```bash tmux new-session -s dev ``` Inside tmux, you can create windows for different tasks: - **Window 0**: Shell / Git operations - **Window 1**: Dev server (`bun run dev`) - **Window 2**: Database / Docker logs - **Window 3**: Testing Detach with `Ctrl+A, D`. Reattach with `tmux attach -t dev`. ## Configure Git ```bash git config --global user.name "Your Name" git config --global user.email "your@email.com" git config --global init.defaultBranch main # Generate SSH key for GitHub ssh-keygen -t ed25519 -C "your@email.com" cat ~/.ssh/id_ed25519.pub # Add this to GitHub → Settings → SSH Keys ``` ## Set Up Zsh with Oh My Zsh I use [Oh My Zsh](https://ohmyz.sh) for a better shell experience — autocompletion, Git integration, and a clean prompt: ```bash # Install zsh sudo apt install -y zsh # Set zsh as default shell chsh -s $(which zsh) # Install Oh My Zsh sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" ``` I use the `robbyrussell` theme (the default) and the `git` plugin. In `~/.zshrc`: ```bash ZSH_THEME="robbyrussell" plugins=(git) source $ZSH/oh-my-zsh.sh ``` The `git` plugin gives you useful aliases like `gst` (git status), `ga` (git add), `gc` (git commit), and tab completion for branch names. ### Shell Aliases I add custom aliases to `~/.zshrc` for common operations: ```bash # Claude Code shortcuts alias cc='claude --dangerously-skip-permissions' alias ccw='claude --dangerously-skip-permissions --worktree' # tmux session management alias tn='tmux new-session -s' # tn myapp alias tl='tmux list-sessions' # list all sessions alias ta='tmux attach-session -t' # ta myapp alias tk='tmux kill-session -t' # tk myapp ``` The `cc` alias launches Claude Code in auto-accept mode — useful when you trust it to make file edits without confirmation prompts. The `ccw` variant does the same but in a Git worktree for isolated work. ## Create a Deploy User For production apps, use a separate user that only runs PM2 processes: ```bash sudo adduser deploy sudo mkdir -p /var/www sudo chown deploy:deploy /var/www ``` This separation means your dev user's environment is isolated from production. Even if something goes wrong in development, your production apps keep running. ## Install Nginx Nginx acts as a reverse proxy in front of your Node.js apps: ```bash sudo apt install -y nginx sudo systemctl enable nginx ``` We'll configure Nginx for specific apps in [Part 5](/blog/hetzner-remote-dev-part-5-production-deployment). ## Directory Structure Here's how I organize things: ``` /home/dev/ ├── projects/ # All development repos │ ├── ahmedelywa.com/ │ ├── my-other-app/ │ └── ... ├── .claude/ # Claude Code config └── .ssh/ # SSH keys /var/www/ # Production deployments (deploy user) ├── ahmedelywa.com/ ├── my-other-app/ └── ecosystem.config.js # PM2 config ``` Development happens in `/home/dev/projects/`. Production deployments go to `/var/www/` under the `deploy` user. Clean separation. ## What's Next The server is now ready for development work. But it's currently accessible via password authentication on the default SSH port — a huge security risk. In [Part 3](/blog/hetzner-remote-dev-part-3-security-with-tailscale), we'll lock it down with Tailscale VPN so it's only accessible through your private network. ## Series Navigation 1. [Choosing Your Server](/blog/hetzner-remote-dev-part-1-choosing-your-server) 2. **Initial Server Setup** (you are here) 3. [Security with Tailscale VPN](/blog/hetzner-remote-dev-part-3-security-with-tailscale) 4. [Claude Code & Development Workflow](/blog/hetzner-remote-dev-part-4-claude-code-dev-workflow) 5. [Production Deployment with GitHub Actions & PM2](/blog/hetzner-remote-dev-part-5-production-deployment)