Enable SSH Key Login and Disable Password Authentication on Ubuntu Server (Hardened Setup)

SSH is still the most common way to manage Linux servers, which also makes it a constant target for brute-force scans and password guessing. One of the simplest hardening steps you can apply on an Ubuntu Server is switching to SSH key authentication and then disabling password login. This tutorial walks through a safe, modern setup that reduces risk without breaking your access.

Why SSH keys are safer than passwords

A password can be guessed, reused, or leaked. SSH keys use public-key cryptography: your server stores a public key, and your client proves it has the matching private key. Even if an attacker targets your SSH service, they cannot “guess” a private key in any realistic timeframe. With password authentication disabled, random login attempts typically fail immediately.

Prerequisites

You need: (1) an Ubuntu Server you can reach over SSH, (2) a user account with sudo privileges, and (3) a local machine (Windows, macOS, or Linux) to generate and store your SSH key. If you’re configuring a remote production server, keep an existing session open until you confirm the new key-based login works.

Step 1: Create a new SSH key on your computer

On Linux/macOS, open a terminal and run:

ssh-keygen -t ed25519 -a 64 -C "[email protected]"

Press Enter to accept the default file path. When prompted, set a passphrase. This protects your private key if your laptop is stolen.

On Windows, you can use Windows Terminal with the built-in OpenSSH client (Windows 10/11). Run the same command above. The key will typically be stored under C:\Users\YourName\.ssh.

Step 2: Copy the public key to the Ubuntu server

Option A (recommended): ssh-copy-id (Linux/macOS, or Windows with WSL):

ssh-copy-id -i ~/.ssh/id_ed25519.pub username@server_ip

This creates (or updates) the remote ~/.ssh/authorized_keys file with correct permissions.

Option B: manual method (works everywhere):

First display your public key locally:

cat ~/.ssh/id_ed25519.pub

Copy the entire output (starts with ssh-ed25519). Then SSH into the server with your current method (likely password), and run:

mkdir -p ~/.ssh && chmod 700 ~/.ssh

nano ~/.ssh/authorized_keys

Paste the public key on a new line, save, then lock down permissions:

chmod 600 ~/.ssh/authorized_keys

Step 3: Test key-based SSH login (don’t skip this)

Before changing any server settings, open a new terminal window and test:

ssh username@server_ip

If your key is picked up correctly, you should either log in directly or be prompted for your key’s passphrase (not the server password). If it still asks for the server password, stop here and troubleshoot the key path and permissions.

Step 4: Disable password authentication in SSHD

On the Ubuntu server, edit the SSH daemon configuration:

sudo nano /etc/ssh/sshd_config

Set (or add) these lines. Be careful to avoid duplicates; if the same setting appears multiple times, the last one usually wins.

PasswordAuthentication no

PubkeyAuthentication yes

If you also want to block direct root logins (recommended), set:

PermitRootLogin no

Save the file and validate the configuration syntax:

sudo sshd -t

If there’s no output, the syntax is OK. Now restart SSH safely:

sudo systemctl restart ssh

Step 5: Confirm you can still access the server

Open another fresh SSH connection from your computer and confirm login works. Keep your original session open until you confirm this step. Once verified, password login attempts should fail with messages like “Permission denied (publickey).”

Optional hardening: limit who can SSH in

If only specific users should access the server, add an allow-list in /etc/ssh/sshd_config:

AllowUsers adminuser deployuser

Restart SSH again after changes. This is especially useful on multi-user servers or internet-facing VPS instances.

Troubleshooting tips

If key login fails, the most common causes are incorrect permissions or the wrong username. On the server, permissions should be 700 on ~/.ssh and 600 on authorized_keys. On the client, make sure you’re using the right key (try ssh -i ~/.ssh/id_ed25519 username@server_ip). For deeper insight, run:

ssh -vvv username@server_ip

The verbose output shows which keys are offered and why authentication succeeds or fails.

Wrap-up

By enabling SSH key login and disabling password authentication, you remove the easiest path attackers use to break into servers. This change is fast, reversible, and one of the best “bang for the buck” security improvements you can make on Ubuntu Server. Once it’s in place, consider adding firewall rules (UFW), automatic updates, and intrusion protection like Fail2ban for an even stronger baseline.

How to Deploy a Private AI Chatbot with Ollama and Open WebUI on Ubuntu (Docker)

Why run a private AI chatbot?

If you like the convenience of ChatGPT-style assistants but need better privacy, lower latency on your local network, or predictable costs, a self-hosted setup is a strong option. With Ollama you can run modern large language models (LLMs) locally, and with Open WebUI you get a clean web interface for chatting, managing models, and organizing prompts. In this tutorial you will deploy both on an Ubuntu server using Docker, so the install is repeatable and easy to maintain.

What you will build

By the end, you will have:

1) Ollama running as a service (the model runtime)
2) Open WebUI running in Docker (the chat UI)
3) Persistent storage for models and chat data
4) Optional GPU support notes if your server has NVIDIA

Prerequisites

Use an Ubuntu 22.04/24.04 server (VM or bare metal). A modern CPU and at least 8 GB RAM is workable for smaller models; 16–32 GB is more comfortable. You also need a user with sudo rights, outbound internet access to pull images/models, and Docker installed. If you plan to expose the UI beyond your LAN, put it behind a reverse proxy with TLS.

Step 1: Install Docker and Docker Compose

First, install Docker from Ubuntu’s repository (simple and reliable for most homelab and SMB setups):

Commands:
sudo apt update
sudo apt install -y docker.io docker-compose-plugin
sudo systemctl enable --now docker

Add your user to the docker group so you can run Docker without sudo (log out/in after this):

Command:
sudo usermod -aG docker $USER

Step 2: Create folders for persistent data

Persistent volumes are important because LLM files can be large and you do not want to re-download models after every container update. Create a working directory:

Commands:
mkdir -p ~/ai-stack/ollama
mkdir -p ~/ai-stack/openwebui
cd ~/ai-stack

Step 3: Create a Docker Compose file

Create a file named docker-compose.yml in ~/ai-stack. This setup runs Ollama and Open WebUI on the same Docker network. Ollama will listen on port 11434 internally; Open WebUI will be published on port 3000.

docker-compose.yml:

Copy and paste:
version: "3.8"

services:
  ollama:
    image: ollama/ollama:latest
    container_name: ollama
    restart: unless-stopped
    volumes:
      - ./ollama:/root/.ollama
    ports:
      - "11434:11434"

  openwebui:
    image: ghcr.io/open-webui/open-webui:main
    container_name: openwebui
    restart: unless-stopped
    depends_on:
      - ollama
    environment:
      - OLLAMA_BASE_URL=http://ollama:11434
    volumes:
      - ./openwebui:/app/backend/data
    ports:
      - "3000:8080"

Step 4: Start the services

Bring the stack up in detached mode:

Command:
docker compose up -d

Verify containers are running:

Command:
docker ps

Step 5: Open the Web UI and pull a model

In a browser, open:

http://YOUR_SERVER_IP:3000

Open WebUI will ask you to create an admin account on first run. After login, you can download models through the interface, or you can pull models from the server side using Ollama.

To pull a popular small model (good for testing), run:

Command:
docker exec -it ollama ollama pull llama3.2

Once the model is downloaded, refresh Open WebUI and select the model for chat. If you want a lighter footprint, try smaller parameter models; if you need better answers, larger models require more RAM/VRAM.

Step 6: Basic troubleshooting (the common issues)

Open WebUI loads but shows no models: Confirm the environment variable points to Ollama. Run docker logs openwebui and make sure it can reach http://ollama:11434. Also verify Ollama is healthy with curl http://localhost:11434 on the host.

Model downloads are slow or fail: Check disk space (df -h) and DNS connectivity. LLM downloads can be multiple gigabytes, so a nearly full disk will cause strange errors.

High CPU and slow replies: This is normal on CPU-only servers with larger models. Use a smaller model, reduce concurrent users, or add GPU acceleration.

Optional: NVIDIA GPU acceleration notes

If you have an NVIDIA GPU, install the NVIDIA driver and the NVIDIA Container Toolkit so Docker containers can access the GPU. Then adjust the Ollama service to request GPU resources (exact configuration depends on your Docker and driver versions). GPU support can dramatically improve response time and allow you to run larger models smoothly.

Step 7: Keep it secure and maintainable

For a safer deployment, do not expose port 3000 directly to the internet. Put Open WebUI behind Nginx or Caddy with HTTPS and authentication. For updates, pull new images and recreate containers:

Commands:
cd ~/ai-stack
docker compose pull
docker compose up -d

Because you used persistent volumes, your downloaded models and chat database stay intact across updates.

Wrap-up

Running Ollama with Open WebUI on Ubuntu gives you a practical private AI chatbot you can use for internal documentation, code explanations, drafting emails, and brainstorming without sending prompts to a third-party cloud service. Start with a smaller model to confirm everything works, then scale up based on your hardware and the quality you need.

3.

How to Set Up WireGuard VPN on Ubuntu Server 24.04 (Secure Remote Access in 15 Minutes)

Why WireGuard is a smart VPN choice in 2026

WireGuard is a modern VPN that focuses on speed, simplicity, and strong security. Compared to traditional VPN stacks, it uses fewer lines of code, performs well on low-cost VPS servers, and is easy to troubleshoot. This tutorial shows how to install and configure WireGuard on Ubuntu Server 24.04 so you can safely access your home or office network, manage servers remotely, and protect traffic on public Wi‑Fi.

What you need before starting

You will need: (1) an Ubuntu Server 24.04 machine with root or sudo access, (2) a public IP address or a router that can forward ports to the VPN server, and (3) a client device (Linux, Windows, macOS, Android, or iOS). If your server is behind NAT (common at home), you must forward a UDP port from your router to the server’s local IP.

Step 1: Update the server and install WireGuard

Start by updating packages and installing WireGuard and the helper tools. On Ubuntu 24.04, WireGuard is included in the standard repositories.

Run:

sudo apt update && sudo apt -y upgrade
sudo apt -y install wireguard

Step 2: Generate server keys (securely)

WireGuard uses public/private key pairs. Keep private keys secret and never paste them into tickets or chat. Create a dedicated directory and lock down permissions.

sudo -i
umask 077
mkdir -p /etc/wireguard
cd /etc/wireguard
wg genkey | tee server.key | wg pubkey > server.pub

You can view the public key with cat /etc/wireguard/server.pub. Avoid printing the private key unless absolutely necessary.

Step 3: Create the WireGuard server configuration

WireGuard’s default interface name is commonly wg0. Pick a private VPN subnet that does not conflict with your LAN. In this example, the VPN network is 10.10.10.0/24, and the server’s VPN IP is 10.10.10.1.

Create the config file:

nano /etc/wireguard/wg0.conf

Paste and adjust the following:

[Interface]
Address = 10.10.10.1/24
ListenPort = 51820
PrivateKey = YOUR_SERVER_PRIVATE_KEY

# Enable NAT so VPN clients can reach the internet (optional but common)
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

Replace YOUR_SERVER_PRIVATE_KEY with the content of /etc/wireguard/server.key. Also verify the server’s main network interface name. On many systems it is eth0, but it might be ens3, enp0s3, or similar. Check with ip a and update the PostUp/PostDown lines accordingly.

Step 4: Enable IP forwarding

If you want VPN clients to reach other networks (like the internet or your LAN), enable IP forwarding.

echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl --system

Step 5: Create a client profile and add it to the server

Now generate keys for one client (repeat for each device). This example creates a client named laptop1 with VPN IP 10.10.10.2.

cd /etc/wireguard
wg genkey | tee laptop1.key | wg pubkey > laptop1.pub

Edit the server config and add a peer section at the bottom:

nano /etc/wireguard/wg0.conf

[Peer]
PublicKey = CLIENT_PUBLIC_KEY
AllowedIPs = 10.10.10.2/32

Replace CLIENT_PUBLIC_KEY with the content of laptop1.pub.

Step 6: Start WireGuard and enable it on boot

Bring up the VPN interface and ensure it starts automatically after reboots.

sudo systemctl enable --now wg-quick@wg0
sudo wg show

The wg show output is your first checkpoint. If the service fails, run sudo systemctl status wg-quick@wg0 to see exactly what went wrong (wrong interface name, missing key, or syntax issues are the usual suspects).

Step 7: Build the client configuration

Create a WireGuard client config file on your client device (or generate it on the server and copy it securely). You will need the server’s public key, the client’s private key, and your server’s public IP or DNS name.

Client config example:

[Interface]
Address = 10.10.10.2/32
PrivateKey = CLIENT_PRIVATE_KEY
DNS = 1.1.1.1

[Peer]
PublicKey = SERVER_PUBLIC_KEY
Endpoint = YOUR_SERVER_PUBLIC_IP:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25

If you only want access to your private networks (and not route all traffic through the VPN), change AllowedIPs to your LAN subnet, for example 192.168.1.0/24, and keep 10.10.10.0/24 as needed. The PersistentKeepalive value helps mobile clients stay connected behind NAT.

Troubleshooting tips that save time

If the VPN connects but you cannot reach anything, check these items in order: (1) confirm UDP port 51820 is open/forwarded to the server, (2) verify your PostUp interface name matches the real outbound interface, (3) confirm IP forwarding is enabled, and (4) make sure the client’s AllowedIPs matches the routing you expect. Also review your firewall rules. On Ubuntu, you may need to allow the UDP port: sudo ufw allow 51820/udp. Finally, re-check keys; one incorrect character in a key line will prevent a proper handshake.

Next steps (best practices)

Once your first client works, add additional peers one at a time and assign each a unique VPN IP. Use a DNS name for the server if your IP changes often. Keep your system updated and consider restricting management access (SSH) to VPN-only for stronger security. WireGuard is lightweight enough to run on a small VPS, making it a practical “always-on” remote access solution for admins and power users.

Configure WireGuard VPN on Ubuntu Server 24.04 (With Clients, Firewall, and Split Tunneling)

Why WireGuard for a Modern VPN?

WireGuard has become a go-to VPN choice because it is fast, lightweight, and easier to maintain than many traditional VPN stacks. It uses modern cryptography, keeps configuration simple (a few keys and IPs), and performs well on cloud servers and home labs. In this tutorial, you will set up a secure WireGuard VPN server on Ubuntu Server 24.04, add clients, lock it down with a firewall, and optionally configure split tunneling so only specific traffic goes through the VPN.

What You Need

Before starting, make sure you have: (1) an Ubuntu Server 24.04 machine with sudo access, (2) a public IP address or a DNS name (for remote access), (3) UDP port 51820 available (or another port you choose), and (4) IP forwarding allowed (we will enable it). These steps work on a VPS and on-prem servers; for home routers you will also need port forwarding.

Step 1: Install WireGuard

Update packages and install WireGuard:

sudo apt update && sudo apt install -y wireguard

Ubuntu 24.04 ships with modern kernels and WireGuard support, so you don’t need extra repositories.

Step 2: Generate Server Keys

Create a secure directory and generate keys:

sudo umask 077
sudo mkdir -p /etc/wireguard
cd /etc/wireguard
sudo wg genkey | sudo tee server_private.key | sudo wg pubkey | sudo tee server_public.key

Your private key must remain secret. The public key will be shared with clients.

Step 3: Create the Server Configuration (wg0)

Decide on a VPN subnet. A common choice is 10.10.0.0/24. Create /etc/wireguard/wg0.conf:

sudo nano /etc/wireguard/wg0.conf

Paste and adjust the following (replace eth0 if your interface name differs):

[Interface]
Address = 10.10.0.1/24
ListenPort = 51820
PrivateKey = (paste contents of /etc/wireguard/server_private.key)
PostUp = ufw route allow in on wg0 out on eth0
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

This enables NAT so VPN clients can reach the internet through the server (useful for secure browsing on public Wi-Fi). If you only need access to internal networks, you can skip NAT later and use routing instead.

Step 4: Enable IP Forwarding

Enable forwarding so the server can route traffic:

sudo nano /etc/sysctl.conf

Uncomment or add:

net.ipv4.ip_forward=1

Apply the change:

sudo sysctl -p

Step 5: Configure UFW Firewall

Allow SSH (if needed) and WireGuard’s UDP port:

sudo ufw allow OpenSSH
sudo ufw allow 51820/udp

Enable the firewall:

sudo ufw enable

If you are on a cloud provider, also open the same UDP port in the provider’s security group/firewall.

Step 6: Start WireGuard and Enable Autostart

Bring up the interface and enable it on boot:

sudo systemctl enable --now wg-quick@wg0

Verify status:

sudo wg
ip a show wg0

Step 7: Add a Client (Laptop/Phone)

On the server, generate a client key pair (example: client1):

cd /etc/wireguard
sudo wg genkey | sudo tee client1_private.key | sudo wg pubkey | sudo tee client1_public.key

Now add the client as a peer to the server. Edit /etc/wireguard/wg0.conf and append:

[Peer]
PublicKey = (paste contents of client1_public.key)
AllowedIPs = 10.10.0.2/32

Apply changes without dropping the tunnel:

sudo wg syncconf wg0 <(sudo wg-quick strip wg0)

Step 8: Create the Client Configuration

On your client device (or on the server to copy later), create a config named client1.conf:

[Interface]
PrivateKey = (paste contents of client1_private.key)
Address = 10.10.0.2/32
DNS = 1.1.1.1

[Peer]
PublicKey = (paste contents of server_public.key)
Endpoint = YOUR_SERVER_IP_OR_DNS:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25

The setting AllowedIPs = 0.0.0.0/0 routes all traffic through the VPN (full tunnel). PersistentKeepalive helps devices behind NAT stay connected.

Optional: Split Tunneling (Route Only What You Need)

If you only want access to the VPN subnet (and keep normal internet direct), change the client’s AllowedIPs to:

AllowedIPs = 10.10.0.0/24

If you need access to a private LAN behind the server (for example 192.168.1.0/24), add it:

AllowedIPs = 10.10.0.0/24, 192.168.1.0/24

Troubleshooting Tips

If the handshake does not happen, first confirm UDP port access from the internet and double-check the Endpoint. Run sudo wg on the server to see “latest handshake” timestamps. If clients connect but cannot browse the internet, re-check NAT rules and that IP forwarding is enabled. Also confirm your server interface name (use ip route to find it) and replace eth0 in the config if needed.

Next Steps

Once your first client works, repeat the peer/client steps for additional devices, giving each client a unique VPN IP (10.10.0.3/32, 10.10.0.4/32, and so on). For easier operations at scale, consider keeping a simple IP assignment list and backing up /etc/wireguard. With this setup, you now have a modern VPN that is fast, secure, and straightforward to maintain.

How to Deploy a Private AI Assistant with Ollama and Open WebUI on Ubuntu Server (Docker)

Overview

If you want an AI assistant for internal documentation, troubleshooting, or drafting replies without sending company data to a third-party cloud, a self-hosted setup is a strong option. In this tutorial, you will deploy a private AI stack on an Ubuntu Server using Docker: Ollama (to run large language models locally) and Open WebUI (a clean web interface for chatting, prompts, and basic management). This approach is practical for homelabs and small teams, and it keeps your prompts and conversation history inside your own network.

What You Will Build

By the end, you will have two containers running: one for Ollama (the model runtime/API) and one for Open WebUI (the front-end). You will also configure persistent storage, pull a model, and confirm everything works from a browser. The steps below are written for Ubuntu Server 22.04/24.04, but will work on most modern Ubuntu releases.

Prerequisites

You need an Ubuntu Server with at least 8 GB RAM (16 GB recommended), 20+ GB free disk, and a modern CPU. A GPU helps performance but is not required for a functional deployment. You also need root or sudo access and a working network connection. If this is a server on a LAN, decide which port you will expose for the web interface (we will use 3000).

Step 1: Install Docker and Docker Compose

First, install Docker using the official repository packages. This ensures you get up-to-date components and fewer compatibility issues with Compose.

Run:

sudo apt update
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

Optionally allow your user to run Docker without sudo (log out and back in afterward):

sudo usermod -aG docker $USER

Step 2: Create a Project Folder and Compose File

Create a directory to keep your deployment clean and manageable. Then create a docker-compose.yml file that defines both services and persistent volumes.

mkdir -p ~/ai-stack
cd ~/ai-stack
nano docker-compose.yml

Paste the following Compose configuration:

services:
  ollama:
    image: ollama/ollama:latest
    container_name: ollama
    restart: unless-stopped
    ports:
      - "11434:11434"
    volumes:
      - ollama:/root/.ollama

  open-webui:
    image: ghcr.io/open-webui/open-webui:main
    container_name: open-webui
    restart: unless-stopped
    ports:
      - "3000:8080"
    environment:
      - OLLAMA_BASE_URL=http://ollama:11434
    volumes:
      - openwebui:/app/backend/data
    depends_on:
      - ollama

volumes:
  ollama:
  openwebui:

Step 3: Start the Stack

Bring up the containers in the background and confirm they are running.

docker compose up -d
docker compose ps

If you see both services with a “running” state, the base deployment is complete.

Step 4: Pull a Model with Ollama

Now download a model. The best choice depends on your RAM and use case. For many servers, a smaller model is a safe starting point. The command below pulls a popular lightweight model.

docker exec -it ollama ollama pull llama3.2

You can list installed models anytime:

docker exec -it ollama ollama list

Step 5: Log In to Open WebUI and Connect to Ollama

Open a browser and go to http://SERVER-IP:3000. On first launch, Open WebUI asks you to create an admin account. After login, the interface should automatically detect Ollama through the internal Docker network using the OLLAMA_BASE_URL you configured.

Start a new chat, select the model you pulled (for example llama3.2), and send a test prompt such as “Write a short troubleshooting checklist for DNS issues.” If the response appears, your private AI assistant is working end-to-end.

Step 6: Basic Hardening and Practical Tips

Firewall: If this is a public-facing server, do not expose it directly without protection. At minimum, allow only your LAN or VPN subnet to reach port 3000. With UFW, you can restrict access instead of opening the port to everyone.

Reverse proxy: For production use, place Open WebUI behind Nginx or Caddy with HTTPS and authentication. This also makes it easier to use a friendly hostname.

Backups: Your important data lives in Docker volumes. Back up the Open WebUI volume (chat history, settings) and the Ollama volume (models) according to your retention needs.

Updates: Refresh images regularly to get security fixes and new features:

docker compose pull
docker compose up -d

Troubleshooting

Open WebUI loads but no models appear: Verify Ollama is reachable from the Open WebUI container. Check logs with docker logs open-webui and confirm OLLAMA_BASE_URL=http://ollama:11434 is correct.

Model downloads are slow: Large model pulls can take time. Ensure your server has stable internet and enough free disk. You can also choose smaller models to start.

High RAM usage or slow responses: Use a smaller model, reduce concurrent users, or run the service on hardware with more memory. Local AI is resource-intensive by design, and tuning is part of a realistic deployment.

Conclusion

Running Ollama and Open WebUI on Ubuntu Server gives you a private, self-hosted AI assistant that you can control, secure, and integrate into your workflow. Once the base stack is stable, you can expand it with HTTPS, SSO, logging, and routine backups. The key advantage is simple: your prompts and internal context stay on your infrastructure while still giving your team an easy web-based AI experience.

How to Deploy a Secure WireGuard VPN Server on Ubuntu 24.04 (With Client Setup)

Why WireGuard and Why Now?

WireGuard has become one of the most practical VPN technologies for modern networks because it is fast, lightweight, and easier to audit than older VPN stacks. For remote work, home labs, or small business admin access, a WireGuard server on Ubuntu 24.04 is a clean way to reach internal services without exposing them directly to the internet. This tutorial walks through a secure, real-world setup: server installation, firewall and forwarding, client configuration, and a few troubleshooting checks.

What You Need Before You Start

You will need an Ubuntu 24.04 server with root or sudo access, a public IPv4 address (or port-forwarding from your router), and a client device (Windows, macOS, Linux, Android, or iOS). Make sure you know your server’s public IP or DNS name. In this guide, we’ll use a private VPN subnet of 10.10.10.0/24 and the server will be 10.10.10.1.

Step 1: Install WireGuard on Ubuntu 24.04

Update packages and install WireGuard and basic firewall tooling:

Commands:
sudo apt update
sudo apt install -y wireguard ufw

Step 2: Generate Server Keys

WireGuard uses public key cryptography. Generate a private/public key pair for the server and protect the private key permissions:

Commands:
sudo umask 077
wg genkey | sudo tee /etc/wireguard/server.key | wg pubkey | sudo tee /etc/wireguard/server.pub

View the public key (you’ll share this with clients):

Command:
sudo cat /etc/wireguard/server.pub

Step 3: Create the WireGuard Interface Configuration

Create /etc/wireguard/wg0.conf. Replace YOUR_SERVER_PRIVATE_KEY with the contents of /etc/wireguard/server.key. If your server’s network interface is not eth0, replace it accordingly (common alternatives are ens3, enp1s0, etc.).

Command:
sudo nano /etc/wireguard/wg0.conf

Example wg0.conf:
[Interface]
Address = 10.10.10.1/24
ListenPort = 51820
PrivateKey = YOUR_SERVER_PRIVATE_KEY

PostUp = ufw route allow in on wg0 out on eth0; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = ufw route delete allow in on wg0 out on eth0; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

This configuration enables NAT so VPN clients can reach the internet or other networks through the server. If you only want access to internal resources and do not need internet tunneling, you can skip the NAT portion and route traffic differently, but NAT is the most common starter setup.

Step 4: Enable IP Forwarding

To route packets between the VPN interface and your main network interface, enable IPv4 forwarding:

Commands:
echo 'net.ipv4.ip_forward=1' | sudo tee /etc/sysctl.d/99-wireguard-forward.conf
sudo sysctl --system

Step 5: Configure the Firewall (UFW)

Allow the WireGuard UDP port and enable the firewall:

Commands:
sudo ufw allow 51820/udp
sudo ufw allow OpenSSH
sudo ufw enable
sudo ufw status

If SSH is not already allowed and you are connected remotely, ensure OpenSSH is permitted before enabling UFW to avoid locking yourself out.

Step 6: Start and Enable the WireGuard Service

Bring up the interface and configure it to start at boot:

Commands:
sudo systemctl enable --now wg-quick@wg0
sudo wg show

The wg show output is your first verification point. At this stage you will not see peers yet, which is normal.

Step 7: Create a Client (Peer) Configuration

On your client device (or on the server if you prefer and then copy files securely), generate client keys. On Linux, you can run:

Commands (client side):
umask 077
wg genkey | tee client1.key | wg pubkey | tee client1.pub

Now add the client as a peer on the server by editing /etc/wireguard/wg0.conf and appending a [Peer] block. Replace CLIENT1_PUBLIC_KEY with the contents of client1.pub:

Server wg0.conf (append):
[Peer]
PublicKey = CLIENT1_PUBLIC_KEY
AllowedIPs = 10.10.10.2/32

Restart WireGuard to apply changes:

Command:
sudo systemctl restart wg-quick@wg0

Step 8: Build the Client VPN Profile

Create a client configuration file (for example client1.conf) and import it into the WireGuard app (Windows/macOS) or WireGuard mobile app (Android/iOS). Replace placeholders with your real values:

Example client1.conf:
[Interface]
PrivateKey = CLIENT1_PRIVATE_KEY
Address = 10.10.10.2/32
DNS = 1.1.1.1

[Peer]
PublicKey = SERVER_PUBLIC_KEY
Endpoint = YOUR_SERVER_PUBLIC_IP_OR_DNS:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25

If you only want access to your internal network and not full tunneling, replace AllowedIPs = 0.0.0.0/0 with only the networks you want to reach (for example 192.168.1.0/24 and 10.10.10.0/24). Keeping AllowedIPs tight is a simple way to reduce risk and avoid routing surprises.

Step 9: Verify the Connection and Troubleshoot

After activating the tunnel on the client, run these checks on the server:

Commands:
sudo wg show
sudo ss -lunp | grep 51820

In wg show, look for a recent latest handshake time and increasing transfer counters. If the handshake never happens, confirm UDP port 51820 is reachable from the internet (cloud security group, router port-forwarding, ISP restrictions). If handshake works but you cannot browse, re-check NAT rules, IP forwarding, and the client’s AllowedIPs. Also confirm your main interface name is correct in the PostUp/PostDown rules.

Security Tips for a Cleaner VPN Deployment

Keep your server updated, use SSH keys instead of passwords, and consider installing Fail2ban for SSH hardening. For WireGuard itself, the strongest control is peer management: only add the peers you need, assign each peer a single /32 address, and remove peers immediately when a device is lost or a user no longer needs access. WireGuard is simple by design, so good operational habits make the biggest difference.

Once this is working, you can expand the setup by adding more peers, routing to additional internal subnets, or placing WireGuard behind a firewall appliance. But even as-is, this Ubuntu 24.04 WireGuard server provides a modern, reliable VPN foundation for secure remote access.

3.

Configure a WireGuard Site-to-Site VPN on Linux (Ubuntu/Debian) with Persistent Routing

Why WireGuard for a Site-to-Site VPN?

WireGuard has become one of the most practical VPN choices for modern Linux environments because it is fast, secure, and easy to troubleshoot. Unlike many older VPN stacks, WireGuard uses a small codebase and straightforward configuration files. In this tutorial, you will set up a site-to-site WireGuard VPN between two Linux servers (or gateways) so that two private networks can reach each other reliably, even after reboots.

Example scenario (adjust to your environment): Site A has LAN 10.10.0.0/24 and a Linux gateway with public IP A_PUBLIC. Site B has LAN 10.20.0.0/24 and a Linux gateway with public IP B_PUBLIC. WireGuard tunnel network will be 10.99.0.0/24, using 10.99.0.1 on Site A and 10.99.0.2 on Site B.

Prerequisites

You need root (or sudo) access on both gateways, outbound UDP allowed, and ideally a static public IP or stable DNS name for each side. This guide assumes Ubuntu/Debian, but the same concepts apply to other distributions. You should also confirm that each gateway can route traffic for its LAN (common when the gateway is also the LAN router, or when static routes exist on the LAN router pointing to the gateway).

Step 1: Install WireGuard

On both servers, install WireGuard tools:

Command:
sudo apt update && sudo apt install -y wireguard

Step 2: Generate Key Pairs

WireGuard uses public/private key pairs. Generate them on each gateway and store them with correct permissions:

On Site A:
umask 077
wg genkey | tee /etc/wireguard/privatekey | wg pubkey > /etc/wireguard/publickey

On Site B:
umask 077
wg genkey | tee /etc/wireguard/privatekey | wg pubkey > /etc/wireguard/publickey

Display each public key (you will paste it into the opposite side’s config):

Command:
cat /etc/wireguard/publickey

Step 3: Create the WireGuard Interface Config

WireGuard configurations live in /etc/wireguard/. Create wg0.conf on each site. Replace placeholders like A_PRIVATE_KEY, B_PUBLIC_KEY, and public IPs/DNS names.

Site A: /etc/wireguard/wg0.conf

[Interface]
Address = 10.99.0.1/24
ListenPort = 51820
PrivateKey = A_PRIVATE_KEY

[Peer]
PublicKey = B_PUBLIC_KEY
Endpoint = B_PUBLIC:51820
AllowedIPs = 10.99.0.2/32, 10.20.0.0/24
PersistentKeepalive = 25

Site B: /etc/wireguard/wg0.conf

[Interface]
Address = 10.99.0.2/24
ListenPort = 51820
PrivateKey = B_PRIVATE_KEY

[Peer]
PublicKey = A_PUBLIC_KEY
Endpoint = A_PUBLIC:51820
AllowedIPs = 10.99.0.1/32, 10.10.0.0/24
PersistentKeepalive = 25

The key detail for site-to-site routing is AllowedIPs. It tells WireGuard what networks to send through the tunnel. Here, each side includes the other site’s LAN (10.10.0.0/24 or 10.20.0.0/24) so packets are routed correctly.

Step 4: Enable IP Forwarding

If your gateways must pass traffic between LAN and VPN, Linux needs forwarding enabled. On both sites, run:

Command:
sudo sysctl -w net.ipv4.ip_forward=1

To make it persistent across reboots, edit /etc/sysctl.conf (or create a file under /etc/sysctl.d/) and ensure this line exists:

net.ipv4.ip_forward=1

Step 5: Adjust Firewall to Allow WireGuard UDP

WireGuard typically listens on UDP 51820. Allow it on both gateways. If you use UFW:

Command:
sudo ufw allow 51820/udp

If you rely on nftables/iptables, allow inbound UDP 51820 and ensure forwarding is permitted between your LAN interface and wg0. Firewall rules vary by environment, but the goal is consistent: UDP port open and forwarding allowed.

Step 6: Bring Up the Tunnel and Enable Autostart

Start the interface on both sides:

Command:
sudo wg-quick up wg0

Enable it at boot:

Command:
sudo systemctl enable wg-quick@wg0

Step 7: Test Connectivity and Routing

First, verify WireGuard handshake status:

Command:
sudo wg

You should see a recent “latest handshake” timestamp after traffic flows. Next, test the tunnel IPs:

From Site A:
ping -c 4 10.99.0.2

From Site B:
ping -c 4 10.99.0.1

Then test LAN-to-LAN reachability. For example, from a host on Site A LAN, ping a host on Site B LAN (or test from the gateway if it can reach the LAN):

Example:
ping -c 4 10.20.0.50

Common Problems (and Quick Fixes)

No handshake: confirm UDP 51820 is reachable from the internet, double-check Endpoint address/port, and ensure the correct public keys are pasted. A mismatched key is the fastest way to waste an hour.

Handshake works but LAN traffic fails: this is usually routing or firewall forwarding. Confirm IP forwarding is enabled and that your firewall allows forwarding between LAN and wg0. Also verify that each peer’s AllowedIPs includes the remote LAN subnet.

Remote LAN devices don’t know the return route: if your WireGuard box is not the default router for the LAN, you may need a static route on the LAN router (e.g., route 10.20.0.0/24 via the Site A WireGuard gateway IP, and vice versa).

Final Notes for a Stable Production Setup

For long-term reliability, keep configs simple and document your addressing plan. Consider using DNS names for Endpoints if IPs change, but make sure DNS is stable. Once everything works, capture the working configuration and back up /etc/wireguard/ securely, since private keys are sensitive. With the tunnel online, you can extend this design to multiple sites or add policy-based firewall rules to limit traffic between subnets.

How to Build a Self-Hosted S3-Compatible Backup Server with MinIO on Ubuntu (and Sync with rclone)

Cloud storage is convenient, but it is not always the best fit for large backups, privacy-focused environments, or labs where you want full control. A practical alternative is running your own S3-compatible object storage. In this tutorial, you will set up MinIO (an S3-compatible object storage server) on Ubuntu Server, secure it with a firewall, and then use rclone to sync backups to your new storage. The result is a modern backup target that works with many tools that already support Amazon S3.

What you will build

You will install MinIO as a system service, create a dedicated data directory, enable the MinIO web console, and harden access. Then you will configure rclone to push a local folder (your backups) into a bucket. This approach is useful for home labs, small businesses, and IT teams that want S3-style APIs without paying per-GB cloud fees.

Prerequisites

You need an Ubuntu Server (20.04/22.04/24.04 are fine), a user with sudo privileges, and at least one disk with enough space for your backup data. For best performance and safety, use a separate disk or mount point (for example, /mnt/minio). You should also know the server’s IP address and have SSH access.

Step 1: Create a MinIO user and storage path

First, create a dedicated system user and a directory for your objects. Keeping MinIO isolated makes permissions and troubleshooting much easier.

Commands:

sudo useradd --system --home /etc/minio --shell /sbin/nologin minio
sudo mkdir -p /mnt/minio
sudo chown -R minio:minio /mnt/minio

Step 2: Install the MinIO server binary

MinIO distributes a single binary. Download it, place it in /usr/local/bin, and make it executable.

cd /tmp
curl -LO https://dl.min.io/server/minio/release/linux-amd64/minio
sudo install minio /usr/local/bin/minio

Step 3: Create a configuration file

MinIO reads environment variables from a config file. Create /etc/default/minio and define your storage path, console port, and admin credentials. Use a strong password and store it safely.

sudo nano /etc/default/minio

Example configuration:

MINIO_VOLUMES="/mnt/minio"
MINIO_OPTS="--console-address :9001 --address :9000"
MINIO_ROOT_USER="minioadmin"
MINIO_ROOT_PASSWORD="CHANGE_THIS_TO_A_LONG_RANDOM_PASSWORD"

Then lock down permissions so regular users cannot read secrets.

sudo chown root:root /etc/default/minio
sudo chmod 600 /etc/default/minio

Step 4: Install a systemd service for MinIO

Running MinIO under systemd gives you automatic restarts and clean startup on boot.

sudo nano /etc/systemd/system/minio.service

Paste this service file:

[Unit]
Description=MinIO
After=network-online.target
Wants=network-online.target

[Service]
User=minio
Group=minio
EnvironmentFile=-/etc/default/minio
ExecStart=/usr/local/bin/minio server $MINIO_OPTS $MINIO_VOLUMES
Restart=always
LimitNOFILE=65536
TasksMax=infinity

[Install]
WantedBy=multi-user.target

Enable and start the service:

sudo systemctl daemon-reload
sudo systemctl enable --now minio
sudo systemctl status minio --no-pager

Step 5: Open firewall ports (or restrict them)

MinIO uses port 9000 for the S3 API and 9001 for the web console. If you use UFW, open only what you need. If this server is internal-only, restrict access to a management subnet.

sudo ufw allow 9000/tcp
sudo ufw allow 9001/tcp
sudo ufw enable

After this, the console should be reachable at http://SERVER_IP:9001. Log in with the root user and password you set earlier.

Step 6: Create a bucket for backups

Inside the MinIO console, create a bucket such as backups. Buckets are like top-level containers. You can also create separate buckets for endpoints like workstations, servers, or projects.

Step 7: Install and configure rclone on a client

Now install rclone on the machine that will send backups (this can be the same server or a different system). rclone supports S3-compatible endpoints, so it works well with MinIO.

sudo apt update
sudo apt install -y rclone

Run the configuration wizard:

rclone config

Create a new remote, choose s3, then set these key options:

Provider: Minio
Endpoint: http://SERVER_IP:9000
Access key ID / Secret access key: use credentials from MinIO (preferably a dedicated user, not the root account)
Region:

Step 8: Sync a local backup folder to MinIO

Assume your backups are stored in /srv/backups and your bucket is backups. Use sync to mirror the folder to object storage. If you want safer behavior, start with copy first.

rclone sync /srv/backups minio-remote:backups --progress --transfers 8

For ongoing operations, add logging and run it on a schedule with cron or a systemd timer. A simple cron example (daily at 01:30):

crontab -e

30 1 * * * rclone sync /srv/backups minio-remote:backups --log-file=/var/log/rclone-minio.log --log-level INFO

Troubleshooting tips

Console not reachable: verify MinIO is listening on ports 9000/9001 with ss -tulpn | grep 900 and confirm firewall rules.

Access denied from rclone: use a dedicated MinIO user and policy, confirm the access key/secret, and ensure the bucket name matches.

Slow performance: check disk throughput, avoid storing data on the OS disk, and consider using faster NICs or enabling multi-disk MinIO setups for scale.

Next steps

Once the basics work, consider adding HTTPS behind a reverse proxy (Nginx or Caddy), creating separate users and policies per team, and enabling bucket versioning for accidental deletion protection. With MinIO plus rclone, you get a flexible, modern backup target that speaks S3 while staying under your control.

Set Up WireGuard VPN on Ubuntu Server 24.04 with Split Tunneling and QR Codes

Why WireGuard in 2025?

WireGuard is a modern VPN that focuses on speed, clean configuration, and strong cryptography. Compared to older VPN stacks, it is lightweight and easier to audit, which is why it has become a default choice for many admins who need secure remote access without complex tooling. In this tutorial, you will install WireGuard on Ubuntu Server 24.04, create a client profile, enable split tunneling (route only private subnets through the VPN), and generate a QR code for quick setup on mobile devices.

What You Need

Before you start, prepare: (1) an Ubuntu Server 24.04 VPS or on-prem server with root or sudo access, (2) UDP port 51820 allowed on your firewall/security group, (3) a public IP address or a DNS name, and (4) one client device (Windows, macOS, Linux, Android, or iOS). The steps below assume your server has a network interface like eth0. If your interface is different (for example, ens3), adjust the commands accordingly.

Step 1: Install WireGuard Tools

Update your package index and install WireGuard plus a QR utility. The qrencode tool is optional, but it makes mobile onboarding dramatically faster.

Commands:

sudo apt update
sudo apt install -y wireguard qrencode

Step 2: Enable IP Forwarding (Required for Routing)

If you want VPN clients to reach your internal networks (or the internet through the server), IP forwarding must be enabled. For split tunneling to private subnets, forwarding is still required so the server can route traffic between the VPN interface and your LAN/WAN interface.

Commands:

echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl --system

Step 3: Generate Server Keys

WireGuard uses public/private key pairs. Keep private keys secret. We will store them in the WireGuard directory with strict permissions.

Commands:

sudo install -m 700 -d /etc/wireguard
cd /etc/wireguard
umask 077
wg genkey | sudo tee server.key | wg pubkey | sudo tee server.pub

Step 4: Create the Server Configuration (wg0.conf)

We will create a VPN subnet, for example 10.10.10.0/24. The server will use 10.10.10.1. For split tunneling, clients will only route specific private subnets through the tunnel, such as 192.168.1.0/24 and 10.0.0.0/8. If you also want full-tunnel later, you can expand the AllowedIPs on the client side.

Create /etc/wireguard/wg0.conf:

sudo nano /etc/wireguard/wg0.conf

Paste and adjust:

[Interface]
Address = 10.10.10.1/24
ListenPort = 51820
PrivateKey = (paste contents of /etc/wireguard/server.key)
# Replace eth0 with your public interface
PostUp = ufw route allow in on wg0 out on eth0; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = ufw route delete allow in on wg0 out on eth0; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

Step 5: Allow UDP 51820 in the Firewall

If you use UFW, allow WireGuard’s UDP port. If your hosting provider has an external firewall/security group, open the same port there as well.

Commands:

sudo ufw allow 51820/udp
sudo ufw enable
sudo ufw status

Step 6: Create a Client Profile (Keys + Peer Entry)

Now generate a client key pair, assign an IP like 10.10.10.2, and add the client as a peer in the server config. This example is for one client called laptop1. Repeat the pattern for more users (use a new key pair and a new IP each time).

Commands:

cd /etc/wireguard
umask 077
wg genkey | sudo tee laptop1.key | wg pubkey | sudo tee laptop1.pub

Edit the server config and append a peer block:

sudo nano /etc/wireguard/wg0.conf

Add at the end:

[Peer]
PublicKey = (paste contents of /etc/wireguard/laptop1.pub)
AllowedIPs = 10.10.10.2/32

Step 7: Start WireGuard and Enable It on Boot

Bring up the interface and make sure it persists after reboots. Then confirm WireGuard is listening.

Commands:

sudo systemctl enable --now wg-quick@wg0
sudo wg show
sudo ss -lunp | grep 51820

Step 8: Build the Client Configuration (Split Tunnel)

Create a local file on your admin machine, or generate it on the server and copy it securely. Replace YOUR_SERVER_PUBLIC_IP with your server’s public IP (or DNS name). For split tunneling, set AllowedIPs to only the networks you want routed through the VPN, plus the WireGuard subnet if you want client-to-client visibility.

Example client config (laptop1.conf):

[Interface]
PrivateKey = (paste contents of /etc/wireguard/laptop1.key)
Address = 10.10.10.2/32
DNS = 1.1.1.1

[Peer]
PublicKey = (paste contents of /etc/wireguard/server.pub)
Endpoint = YOUR_SERVER_PUBLIC_IP:51820
AllowedIPs = 10.10.10.0/24, 192.168.1.0/24, 10.0.0.0/8
PersistentKeepalive = 25

Step 9: Generate a QR Code for Mobile Clients

On Android and iOS, the official WireGuard app can import from a QR code. This avoids typos in keys and endpoints. Run qrencode against the client configuration file and scan it in the app.

Commands:

qrencode -t ansiutf8 < laptop1.conf

Troubleshooting Tips

If the tunnel connects but you cannot reach private subnets, check routing on the server and confirm that the destination network knows how to return traffic to 10.10.10.0/24 (either via the WireGuard server as a gateway or via NAT). If handshakes never appear in wg show, verify UDP 51820 is open, confirm your Endpoint is correct, and ensure your server’s clock is accurate (NTP issues can sometimes cause confusing behavior). Finally, if you run another firewall besides UFW, make sure it is not blocking forwarding between wg0 and your outbound interface.

Next Steps

Once your first client works, add more peers and give each one a unique VPN IP. For better security hygiene, keep peer access tight by limiting AllowedIPs to only the subnets each user needs. If you want to manage many devices, consider storing configs in a password manager and rotating keys on a schedule, especially for contractors or short-term users.

Deploy Ollama + Open WebUI on Ubuntu with GPU Acceleration using Docker Compose

Running large language models locally is now practical and fast, especially with GPU acceleration. In this tutorial, you will deploy Ollama and Open WebUI on Ubuntu 22.04/24.04 using Docker Compose. This stack gives you a private, browser-based interface for modern LLMs (Llama, Mistral, Phi, etc.) with one-click model management and secure, self-hosted inference.

Why this stack?

Ollama simplifies downloading, quantizing, and serving LLMs on your machine. Open WebUI adds a clean chat interface, prompt templates, file uploads, and multi-user access. Together, they provide a robust local AI setup that is easy to update and portable across servers.

Prerequisites

- Ubuntu Server 22.04 or 24.04 (fresh system recommended)

- An NVIDIA GPU with recent drivers (T4, RTX 20/30/40, A-series, etc.)

- sudo access and an internet connection

- Optional: a domain name for HTTPS (e.g., ai.example.com)

Step 1 — Install Docker Engine and Compose

Install Docker from the official repository to ensure up-to-date features like GPU support in Docker Compose.

sudo apt update
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
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-buildx-plugin docker-compose-plugin
sudo usermod -aG docker $USER
newgrp docker
docker --version
docker compose version

Step 2 — Enable GPU with NVIDIA Container Toolkit

Install the NVIDIA Container Toolkit to pass the GPU into containers. Verify that the host can see the GPU with nvidia-smi before proceeding.

# If you don't have drivers:
# sudo ubuntu-drivers install && sudo reboot

curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit.gpg
curl -fsSL https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
  sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit.gpg] https://#g' | \
  sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo apt update
sudo apt install -y nvidia-container-toolkit

# Configure Docker to use the NVIDIA runtime
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker

# Sanity check
nvidia-smi

Step 3 — Create the Docker Compose stack

We will run two services: Ollama (backend API on port 11434) and Open WebUI (frontend on port 3000) connected via a Docker network. The compose file also enables GPU support for Ollama.

mkdir -p ~/ollama-openwebui && cd ~/ollama-openwebui
cat > docker-compose.yml <<'YAML'
services:
  ollama:
    image: ollama/ollama:latest
    container_name: ollama
    restart: unless-stopped
    ports:
      - "11434:11434"
    volumes:
      - ollama:/root/.ollama
    gpus: all
    environment:
      - OLLAMA_KEEP_ALIVE=1h
      - OLLAMA_HOST=0.0.0.0

  openwebui:
    image: ghcr.io/open-webui/open-webui:latest
    container_name: openwebui
    restart: unless-stopped
    depends_on:
      - ollama
    ports:
      - "3000:8080"
    environment:
      - OLLAMA_BASE_URL=http://ollama:11434
      - WEBUI_AUTH=True
    volumes:
      - openwebui:/app/backend/data

volumes:
  ollama:
  openwebui:
YAML

Step 4 — Launch and access Open WebUI

Start the stack and watch logs for any errors. The first launch will pull images.

docker compose up -d
docker compose logs -f --tail=100

Open your browser to http://SERVER_IP:3000. Create the first admin user when prompted. Open WebUI will automatically detect Ollama via the internal URL and list available models.

Step 5 — Pull a model and test

Use either the WebUI model manager or the CLI to fetch models. The example below pulls a popular 7B model.

# Pull from the host (proxies into the container)
docker exec -it ollama ollama pull llama3.1:8b

# Quick API smoke test
curl http://localhost:11434/api/generate -d '{
  "model": "llama3.1:8b",
  "prompt": "Say hello from a local LLM.",
  "stream": false
}'

In Open WebUI, select the model from the dropdown and start chatting. If you have enough VRAM, consider quantized larger models (e.g., 13B/70B Q4/Q5) for better reasoning.

Optional — Secure with a Caddy reverse proxy and HTTPS

If you have a domain, use Caddy to obtain and renew TLS automatically. This example exposes Open WebUI securely on port 443 and keeps Ollama private.

sudo apt install -y caddy
sudo tee /etc/caddy/Caddyfile >/dev/null <<'CADDY'
ai.example.com {
  encode zstd gzip
  reverse_proxy 127.0.0.1:3000
}
CADDY
sudo systemctl reload caddy

Point your DNS A/AAAA record to the server. Then visit https://ai.example.com. For teams, enable WebUI auth (already set) and create users from the admin settings.

Back up and update

To back up your models and chats, save the named volumes. You can also snapshot the folders from the host.

# Export volumes to tarballs
docker run --rm -v ollama:/v -v $(pwd):/b busybox tar czf /b/ollama-vol.tgz -C /v .
docker run --rm -v openwebui:/v -v $(pwd):/b busybox tar czf /b/openwebui-vol.tgz -C /v .

# Update images safely
docker compose pull
docker compose up -d

Troubleshooting

- No GPU detected: Ensure nvidia-smi works on the host. Re-run nvidia-ctk runtime configure, restart Docker, and verify the container sees the GPU:

docker exec -it ollama bash -lc 'nvidia-smi || ls -l /dev/nvidia*'

- Slow generation: Use quantized models (Q4_K_M/Q5_K_M), avoid oversize context windows, and confirm GPU is actually used (GPU utilization should rise in nvidia-smi during inference).

- Port conflicts: Change mapped ports in docker-compose.yml, e.g., "3001:8080" for Open WebUI or put a reverse proxy in front.

- Permission errors on volumes: Ensure your user is in the docker group and that the Docker daemon can write to the volume paths.

Security tips

- Keep Ollama bound to the internal network and only expose Open WebUI through TLS.

- Enable authentication (already set via WEBUI_AUTH=True). Use strong passwords and consider putting Open WebUI behind a VPN or SSO.

- Restrict firewall ports using UFW: allow 22/tcp and 443/tcp, then deny others.

Conclusion

You now have a GPU-accelerated, private AI stack with Ollama and Open WebUI on Ubuntu, orchestrated by Docker Compose. It is easy to upgrade, portable across servers, and suitable for personal research or team deployments. With this foundation, you can iterate quickly, evaluate new models as they drop, and keep your data fully on-prem.

How to Deploy a Zero‑Trust WireGuard VPN with Tailscale on Ubuntu Server (2025 Guide)

Overview

This step-by-step guide shows how to deploy a zero-trust VPN using Tailscale (built on WireGuard) on Ubuntu Server. You will install the Tailscale client, log in with SSO, enable secure SSH, configure access control lists (ACLs), expose a private subnet, and optionally offer an exit node. The result is a modern, fast, and secure VPN with minimal maintenance and strong identity-based access controls—perfect for homelabs and production servers in 2025.

Why Tailscale (WireGuard) for Zero Trust

Tailscale uses the WireGuard protocol for speed and strong cryptography while removing the operational pain of traditional VPNs. Devices authenticate using your identity provider (Google, Microsoft, Okta, GitHub, and others) and connect peer-to-peer where possible. You gain per-device keys, automatic NAT traversal, policy-based access (ACLs), MagicDNS, and optional Tailscale SSH that replaces inbound firewall holes.

Prerequisites

You need an Ubuntu Server 22.04 or 24.04 host with sudo access. Ensure outbound HTTPS (TCP 443) and UDP 41641 are allowed. No inbound ports are strictly required. Have a browser handy to authenticate to your identity provider or prepare a reusable auth key from the Tailscale admin console for headless systems.

Install Tailscale on Ubuntu

Run these commands to add the official repository and install Tailscale. The snippet auto-detects your Ubuntu codename:

curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/$(. /etc/os-release; echo $VERSION_CODENAME).noarmor.gpg | sudo tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null

curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/$(. /etc/os-release; echo $VERSION_CODENAME).tailscale-keyring.list | sudo tee /etc/apt/sources.list.d/tailscale.list

sudo apt-get update && sudo apt-get install -y tailscale

Authenticate and bring the node online

For interactive login, start Tailscale and enable Tailscale SSH. This avoids exposing port 22 to the internet and lets you restrict SSH via ACLs:

sudo tailscale up --ssh --operator=$USER --accept-dns=true --accept-routes=true --advertise-tags=tag:server

If the server is headless, generate an auth key in the Tailscale admin console (preferably tagged and reusable or ephemeral) and run:

sudo tailscale up --ssh --authkey=tskey-******** --advertise-tags=tag:server

Verify connectivity with tailscale status, view the device IPs via tailscale ip -4, and test pings to another node using tailscale ping <device-or-name>.

Enable zero-trust access controls (ACLs)

Open the Tailscale admin console and switch to the ACLs page. Keep policies simple and human-readable. Example: allow your admin group to SSH to servers and let developers access staging web ports:

{
  "groups": { "group:admins": ["[email protected]"], "group:devs": ["[email protected]"] },
  "tagOwners": { "tag:server": ["group:admins"] },
  "acls": [
    { "action": "accept", "users": ["group:admins"], "ports": ["tag:server:22,2222"] },
    { "action": "accept", "users": ["group:devs"], "ports": ["tag:server:80,443,8080"] }
  ],
  "ssh": [
    { "action": "check", "src": ["group:admins"], "dst": ["tag:server"], "users": ["root", "ubuntu"] }
  ]
}

This policy makes tag:server devices manageable by admins, grants controlled port access, and restricts Tailscale SSH to authorized identities. Commit and save to enforce instantly.

Expose your LAN with a Subnet Router (optional)

If the Ubuntu host can reach a private LAN (e.g., 192.168.10.0/24), you can advertise that subnet to Tailscale peers without opening your firewall. First, enable IP forwarding:

echo 'net.ipv4.ip_forward=1' | sudo tee /etc/sysctl.d/99-tailscale.conf

echo 'net.ipv6.conf.all.forwarding=1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf

sudo sysctl --system

Now advertise the subnet routes:

sudo tailscale up --advertise-routes=192.168.10.0/24 --accept-routes=true

Approve the routes in the admin console. For UFW, permit forwarding within the LAN and from the Tailscale interface (typically tailscale0):

sudo ufw route allow in on tailscale0 out on eth0 to 192.168.10.0/24

sudo ufw reload

Set up an Exit Node (optional)

An exit node lets approved devices send all internet traffic through your Ubuntu server. This is useful for securing devices on public Wi‑Fi or egressing from a fixed IP. Enable it on the server:

sudo tailscale up --advertise-exit-node=true

In the admin console, allow the exit node and then, from a client, choose “Use exit node”. Optionally permit local LAN access while using the exit node by enabling “Allow LAN access” on the client.

Security hardening and best practices

Restrict who can reach what by tags and groups; avoid broad *:* policies. Prefer Tailscale SSH over public SSH. Disable password auth in OpenSSH (sudoedit /etc/ssh/sshd_config, set PasswordAuthentication no) and restart SSH. Keep the system current and enable unattended upgrades:

sudo apt-get install -y unattended-upgrades

sudo dpkg-reconfigure --priority=low unattended-upgrades

If you use UFW, you generally do not need to open inbound ports for Tailscale. Traffic arrives over the encrypted tunnel and is handled by tailscaled. For large fleets, use ephemeral auth keys for CI/CD runners (--ephemeral) and shorter key lifetimes. Consider using device posture checks and auto-approvers in ACLs where appropriate.

Troubleshooting quick checks

Run sudo tailscale bugreport to gather diagnostics if needed. Use tailscale netcheck to verify NAT traversal, tailscale status to view peers, and tailscale ping to test reachability. If subnets are not reachable, confirm routes are approved and that IP forwarding and UFW rules allow routed traffic. If speeds seem low, ensure direct connections are established (not relayed) and verify that CPU scaling or virtualization offloads are not limiting WireGuard throughput on the server.

What you achieved

You installed a production-ready, zero-trust WireGuard VPN with Tailscale on Ubuntu, authenticated it with SSO, enabled Tailscale SSH, enforced fine-grained ACLs, and optionally provided subnet routing and exit-node functionality. This approach is simpler, faster, and safer than legacy site-to-site or username/password VPNs, and it scales from a single VPS to a multi-site enterprise network with minimal toil.

How to Set Up a Secure VPN with WireGuard on Ubuntu Server

Introduction: In the era of remote work and increased concerns for digital privacy, setting up a Virtual Private Network (VPN) has become more crucial than ever. WireGuard is a modern VPN protocol featuring high security and better performance compared to older protocols. This tutorial will guide you through the process of setting up WireGuard on an Ubuntu Server.

Prerequisites: Before starting, ensure you have the following: an Ubuntu Server (20.04 or later) with root access, a basic understanding of Linux commands, and a public IP address for your server.

Step 1: Install WireGuard

Firstly, you need to install WireGuard on your Ubuntu Server. Open your terminal and run the following commands:

sudo apt update
sudo apt install wireguard
These commands update your package list and install WireGuard.

Step 2: Configure WireGuard

After installation, you need to configure the VPN server settings. Start by generating the private and public keys:

cd /etc/wireguard/
umask 077
wg genkey | tee privatekey | wg pubkey > publickey
Note the key outputs as you will need them later.

Create a new configuration file for your VPN server:

nano wg0.conf
In this file, input the following configuration, adjusting the IP addresses as necessary:
[Interface]
PrivateKey = [Your Server's Private Key]
Address = 10.200.200.1/24
ListenPort = 51820
SaveConfig = true

[Peer]
PublicKey = [Your Peer's Public Key]
AllowedIPs = 10.200.200.2/32
Replace "[Your Server's Private Key]" and "[Your Peer's Public Key]" with the appropriate keys you generated earlier.

Step 3: Enable and Start WireGuard

To enable and start the WireGuard service, use the following commands:

sudo systemctl enable [email protected]
sudo systemctl start [email protected]
This sets the WireGuard service to start at boot and runs it immediately.

Conclusion: You now have a basic WireGuard VPN set up on your Ubuntu Server. This setup provides a secure and private tunnel for your internet traffic. For further customization and security, consider adding firewall rules and configuring additional peers.

Note: Always ensure you comply with local laws and regulations when configuring network services like VPNs.

3.

Popular Posts

Install Ollama and Open WebUI on Ubuntu 24.04 with NVIDIA GPU Acceleration (Step-by-Step)

Install Ollama + Open WebUI on Ubuntu 24.04 with NVIDIA GPU Acceleration (Step-by-Step)

Install a Local AI Chatbot on Ubuntu 24.04 with Ollama and Open WebUI (Step-by-Step)

Trending Now

Debian Adoption at CERN Signals Strong Momentum for Enterprise Linux

By the end of this article readers will understand the implications of CERN’s migration of 2,200 control systems to Debian 13, the performance enhancements in Firefox 155, and recent developments across several Linux distributions that affect system administration and user experience. Debian 13 Deployment at CERN: Scale and Significance The European Organization for Nuclear Research (CERN) has announced the migration of 2,200 of its control systems to Debian 13. This move represents one of the largest coordinated deployments of a Debian release in a scientific research environment. Control systems at CERN are responsible for monitoring and managing critical hardware, from accelerator components to detector subsystems. Their reliability hinges on a stable operating system with long‑term support, predictable update cycles, and a robust package ecosystem. Debian’s reputation for stability and its extensive testing process make it a natural fit for such mission‑critical workloads. Debia...