Introduction: Our VPS Setup Journey

This document chronicles our journey in setting up and troubleshooting a Virtual Private Server (VPS) from scratch. We've navigated through DNS configurations, web server setups, security measures, and various common pitfalls. The goal is to provide a comprehensive guide to getting your website online and secure, detailing each step, the components involved, and how they interact.

From initial domain resolution issues to Nginx configurations and user management, this guide covers the essential elements of self-hosting a web presence.

1. DNS Zone File: The Foundation of Your Domain

The DNS (Domain Name System) zone file is like the phonebook for your domain. It tells the internet how to translate your human-readable domain names (like your-domain.net) into machine-readable IP addresses (like your-vps-ip).

Initial State & Corrections

Our initial zone file had a few issues, including a typo in a record type (CNANE instead of CNAME) and an incomplete IP address. We also learned the importance of incrementing the SOA serial number with every change.


$TTL 86400
@ IN SOA ns1.your-domain.net. hostmaster.your-domain.net. (
    2025052701 ; Serial (Increment this with every change!)
    7200         ; Refresh (2 hours)
    3600         ; Retry (1 hour)
    1209600      ; Expire (2 weeks)
    3600         ; Minimum TTL (1 hour)
)

; Nameservers (Crucial for self-hosted DNS)
@ IN NS ns1.your-domain.net.
@ IN NS ns2.your-domain.net.

; A records for the domain itself (IPv4 & IPv6)
@ IN A your-vps-ip
@ IN AAAA your-vps-ipv6

; Glue Records (Crucial for nameservers on the same domain as the website)
; These tell the internet where to find your authoritative nameservers.
; In a production setup, ns1 and ns2 should ideally have different public IPs for redundancy.
ns1 IN A your-vps-ip
ns2 IN A your-vps-ip

; Common A records for subdomains
www IN A your-vps-ip
mail IN A your-vps-ip
set IN A your-vps-ip
m IN A your-vps-ip

; Corrected: CNAME record for preview_r4g (example, if it points to www)
preview_r4g IN CNAME www.your-domain.net.

; MX record for email (points email for your-domain.net to mail.your-domain.net)
@ IN MX 10 mail.your-domain.net.

; Optional: SPF record for email sender authentication
@ IN TXT "v=spf1 a mx ip4:your-vps-ip ~all"

; Optional: DMARC record (requires SPF and DKIM for full effectiveness)
_dmarc IN TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@your-domain.net;"
                

The final version of the zone file, after correcting typos and ensuring proper syntax, was deemed "syntactically perfect."

VPS Parts Controlled:

  • Domain Name System (DNS) records for your domain.

Access Points:

  • Not directly an access point, but dictates how external users find your services (websites, email).

2. Self-Hosting DNS with Bind9

Deciding to self-host your DNS means your VPS becomes the authoritative source for your domain's records. This requires installing and configuring Bind9 (the named service).

Installation & Service Management

Ensure Bind9 is installed and running. The service name is named.service.


sudo apt update
sudo apt install bind9 bind9utils bind9-doc # For Debian/Ubuntu

sudo systemctl start named.service    # Start the service
sudo systemctl enable named.service   # Enable it to start on boot
sudo systemctl status named.service   # Check its status (should be active/running)
                

`named.conf.local` & Zone File Placement

You need to tell Bind9 to load your domain's zone file. This is done in /etc/bind/named.conf.local.


# In /etc/bind/named.conf.local
zone "your-domain.net" {
    type master;
    file "/etc/bind/db.your-domain.net"; # Path to your zone file
    allow-transfer { none; };    # Restrict zone transfers for security (or list secondary NS IPs)
    allow-query { any; };        # Allow anyone to query your authoritative zones
};
                

Place your zone file (e.g., db.your-domain.net) in the specified path.

`named.conf.options` & Security Hardening

This is crucial for preventing your Bind9 server from becoming an "open recursive resolver," which can be abused for DNS amplification attacks. Explicitly disable recursion for external clients.


# In /etc/bind/named.conf.options
options {
    directory "/var/cache/bind";

    dnssec-validation auto;

    listen-on-v6 { any; }; # Listen on all IPv6 interfaces
    listen-on { any; };    # Listen on all IPv4 interfaces

    allow-recursion { none; }; # CRITICAL: Disallow recursive queries from anyone
    allow-query-cache { none; }; # Prevent cache queries from external clients
    recursion no;          # Explicitly turn off recursion
};
                

Permissions & Testing Bind9

Ensure Bind9 can read its configuration and zone files, and test for syntax errors.


sudo chown bind:bind /etc/bind/db.your-domain.net # Adjust path to your zone file
sudo chmod 644 /etc/bind/db.your-domain.net

sudo named-checkconf                 # Check global Bind9 config syntax
sudo named-checkzone your-domain.net /etc/bind/db.your-domain.net # Check your specific zone file syntax

sudo systemctl restart named.service # Restart Bind9 to apply changes
                

VPS Parts Controlled:

  • DNS Server (Bind9/named)

Access Points:

  • **UDP Port 53:** For standard DNS queries.
  • **TCP Port 53:** For DNS zone transfers and larger queries.

3. Nginx Web Server: Serving Your Website

Nginx is a high-performance web server that excels at serving static content and acting as a reverse proxy. We chose Nginx alone (with PHP-FPM) for its efficiency on a VPS.

Installation & Service Management

Install Nginx and ensure it's running.


sudo apt install nginx # For Debian/Ubuntu

sudo systemctl start nginx    # Start the service
sudo systemctl enable nginx   # Enable it to start on boot
sudo systemctl status nginx   # Check its status (should be active/running)
                

`sites-available` / `sites-enabled` Convention

This is a common and recommended way to manage Nginx (and Apache) virtual host configurations. Create your domain's config file in sites-available and then symlink it to sites-enabled to activate it.


# Create your domain's config file (e.g., your-domain.net.conf)
sudo nano /etc/nginx/sites-available/your-domain.net.conf

# Create a symbolic link to enable the site
sudo ln -s /etc/nginx/sites-available/your-domain.net.conf /etc/nginx/sites-enabled/

# To disable a site, remove the symlink:
# sudo rm /etc/nginx/sites-enabled/your-domain.net.conf
                

`default` Server Block Configuration

The `default` server block acts as a catch-all for requests that don't match any other configured domain. It's good practice to secure it by returning a 444 (No Response) for unmatched traffic.


# In /etc/nginx/sites-available/default
server {
    listen 80 default_server;
    listen [::]:80 default_server;

    server_name _; # Catch-all dummy name

    return 444; # Close connection for unmatched requests
}

# (Optional: A similar block for HTTPS if you need a default SSL catch-all,
# but requires dummy SSL certs to avoid Nginx startup issues.)
# server {
#     listen 443 ssl default_server;
#     listen [::]:443 ssl default_server;
#     server_name _;
#     ssl_certificate /etc/ssl/nginx/default.crt;
#     ssl_certificate_key /etc/ssl/nginx/default.key;
#     return 444;
# }
                

Remember to test Nginx config (`sudo nginx -t`) and reload (`sudo systemctl reload nginx`) after any changes.

VPS Parts Controlled:

  • Web Server (Nginx)

Access Points:

  • **TCP Port 80:** For HTTP web traffic.
  • **TCP Port 443:** For HTTPS/SSL web traffic.

4. PHP-FPM Integration: Dynamic Content

Nginx doesn't process PHP natively. It passes PHP requests to PHP-FPM, a FastCGI Process Manager. This requires both PHP-FPM to be installed and Nginx to be configured to communicate with it.

PHP-FPM Installation & Service Management

Install PHP-FPM (e.g., PHP 8.3) and necessary extensions, then ensure it's running.


sudo apt update
sudo apt install php8.3-fpm php8.3-mysql php8.3-cli # Install PHP 8.3 FPM and MySQL extension

sudo systemctl start php8.3-fpm    # Start the service
sudo systemctl enable php8.3-fpm   # Enable it to start on boot
sudo systemctl status php8.3-fpm   # Check its status
                

Nginx `location ~ \.php$` Block

This block, placed in your domain's Nginx `.conf` file, tells Nginx how to handle `.php` requests by passing them to the PHP-FPM socket.


# In /etc/nginx/sites-available/your-domain.net.conf (within the server block)

# Set the root directory for your website files
root /var/www/your-domain.net/html;

# Define the order of index files (ensure index.php is first)
index index.php index.html index.htm;

# Location block to handle PHP files
location ~ \.php$ {
    # The 'try_files' directive is usually in 'snippets/fastcgi-php.conf',
    # so remove it from here if you encounter a "duplicate" error.
    # try_files $uri =404;

    include snippets/fastcgi-php.conf; # Includes standard FastCGI parameters

    # Pass PHP requests to the PHP-FPM socket.
    # VERIFY THIS PATH: It must match your PHP-FPM version's socket.
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;

    # Define SCRIPT_FILENAME parameter for PHP-FPM
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

# For general requests (important if you're not proxying everything to Apache)
location / {
    try_files $uri $uri/ =404; # Try file, then directory, then 404
}
                

File Permissions for Web Content

Ensure your website files and directories have correct permissions for Nginx and PHP-FPM to read them.


sudo mkdir -p /var/www/your-domain.net/html # Create your document root
sudo chown -R $USER:www-data /var/www/your-domain.net/html # Your user owns, www-data group has access
sudo chmod -R 755 /var/www/your-domain.net/html # Directories 755
sudo chmod 644 /var/www/your-domain.net/html/index.php # Files 644 (example for index.php)
                

VPS Parts Controlled:

  • PHP Processor (PHP-FPM)
  • Web Server (Nginx configuration for PHP)
  • Filesystem (Permissions)

Access Points:

  • **Internal Unix Socket/TCP Port (e.g., 9000):** Nginx communicates with PHP-FPM internally, not directly exposed to the internet.

5. SSL/HTTPS with Certbot: Securing Your Site

Certbot automates obtaining and renewing free SSL/TLS certificates from Let's Encrypt, enabling secure HTTPS connections. It can automatically configure Nginx for you.

Certbot Installation

Install Certbot, preferably using Snap for the latest version and plugin support.


sudo apt update
sudo apt install snapd
sudo snap install core; sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
                

HTTP-01 Challenge & Automatic Nginx Config

Certbot uses the HTTP-01 challenge to verify domain ownership. It requires your domain to be publicly accessible on HTTP (port 80). Certbot's Nginx plugin can then automatically modify your Nginx configuration.


# Ensure Nginx is running and configured for HTTP on your domains
# (e.g., your-domain.net.conf should have a 'listen 80;' block)

# Run Certbot to obtain and install certificates for your domains
# List all domains/subdomains you want in THIS certificate.
sudo certbot --nginx -d your-domain.net -d www.your-domain.net -d mail.your-domain.net -d m.your-domain.net -d preview_r4g.your-domain.net
                

Follow the prompts: enter email, agree to ToS, and choose whether to redirect HTTP to HTTPS (recommended: option 2).

Certificate Locations & Auto-renewal

Certbot places certificates in a standardized location and sets up automatic renewal.


# Certificates are typically stored here:
/etc/letsencrypt/live/your-domain.net/fullchain.pem
/etc/letsencrypt/live/your-domain.net/privkey.pem

# Test automatic renewal (dry run)
sudo certbot renew --dry-run
                

Certbot automatically sets up a cron job or systemd timer to renew certificates before they expire.

VPS Parts Controlled:

  • SSL/TLS Certificates
  • Web Server (Nginx configuration for HTTPS)

Access Points:

  • **TCP Port 443:** For HTTPS/SSL encrypted web traffic.

6. Firewall Management: Securing Network Access

A firewall controls incoming and outgoing network traffic. We'll use UFW (Uncomplicated Firewall) on Debian/Ubuntu to open only necessary ports.

Checking Status & Rules (UFW)

Always check your firewall status before making changes.


sudo ufw status          # Basic status (active/inactive)
sudo ufw status verbose  # More details
sudo ufw status numbered # List rules with numbers (useful for deletion)
                

Opening Essential Ports (UFW)

Allow traffic for SSH, DNS, HTTP, and HTTPS.


sudo ufw allow OpenSSH     # Allows SSH (Port 22)
sudo ufw allow 53/udp      # Allows UDP DNS queries
sudo ufw allow 53/tcp      # Allows TCP DNS queries
sudo ufw allow 'Nginx HTTP'  # Allows HTTP (Port 80)
sudo ufw allow 'Nginx HTTPS' # Allows HTTPS (Port 443)
# OR if you want both HTTP/HTTPS for Nginx:
# sudo ufw allow 'Nginx Full'

sudo ufw enable            # Enable the firewall (CAUTION: Ensure SSH is allowed first!)
sudo ufw reload            # Reload firewall rules to apply changes
                

VPS Parts Controlled:

  • Network Security (UFW/Firewalld)

Access Points:

  • **TCP Port 22:** For SSH access.
  • **UDP Port 53, TCP Port 53:** For DNS queries to your Bind9 server.
  • **TCP Port 80:** For unencrypted web traffic.
  • **TCP Port 443:** For encrypted web traffic.

7. User & Group Management: Secure File Access

Managing users and groups is essential for system administration and security, especially for file transfers via SFTP.

Creating Users & Groups

Create a dedicated group for SFTP users and then create individual user accounts.


sudo groupadd sftpusers          # Create a group for SFTP users

sudo adduser sftpuser1           # Create a new system user
sudo usermod -aG sftpusers sftpuser1 # Add user to the sftpusers group
# Repeat for sftpuser2, etc.
                

SFTP Chroot Jailing (`sshd_config`)

This isolates SFTP users to a specific directory, preventing them from accessing other parts of your server. This is configured in /etc/ssh/sshd_config.


sudo nano /etc/ssh/sshd_config
                

Find and comment out (or remove) the line starting with Subsystem sftp. Then add these lines at the end of the file:


Subsystem sftp internal-sftp

Match Group sftpusers
    ChrootDirectory %h
    X11Forwarding no
    AllowTcpForwarding no
    PermitTunnel no
    AgentForwarding no
    ForceCommand internal-sftp
                

**Crucial Permissions:** The `ChrootDirectory` (e.g., `/home/sftpuser1`) and its parent directories up to `/` **must be owned by root and not writable by anyone else.**


sudo chown root:root /home/sftpuser1 # ChrootDirectory must be owned by root
sudo chmod 755 /home/sftpuser1      # Not writable by group/others

# Create a writable subdirectory for the user's files
sudo mkdir /home/sftpuser1/ftp
sudo chown sftpuser1:sftpusers /home/sftpuser1/ftp # User owns their writable directory
sudo chmod 755 /home/sftpuser1/ftp                 # User and group can write

sudo systemctl restart sshd # Restart SSH service to apply changes
                

Understanding Chroot Security

A chroot jail is a security layer that restricts a process to a specific filesystem subset. It's effective against casual misuse and accidental damage, but it's not an impenetrable boundary against a highly skilled attacker with root privileges inside the jail. It should be part of a broader "defense in depth" strategy, combined with other security measures.

Removing Users & Groups

Use these commands to clean up user and group accounts.


# Remove user and their home directory (recommended for Debian/Ubuntu)
sudo deluser --remove-home username

# Remove a group (ensure no users have it as primary group first)
sudo groupdel groupname
                

Listing Users & Groups

Commands to view existing users and groups on your system.


# List all users (usernames only)
cut -d: -f1 /etc/passwd
# OR (more comprehensive)
getent passwd | cut -d: -f1

# List all groups (group names only)
cut -d: -f1 /etc/group
# OR (more comprehensive)
getent group | cut -d: -f1

# List groups for a specific user
groups username
# OR
id -Gn username
                

VPS Parts Controlled:

  • User Accounts
  • Group Management
  • SFTP Server (SSH Daemon configuration)
  • Filesystem (Permissions)

Access Points:

  • **TCP Port 22:** For SFTP access (via SSH).

8. Troubleshooting & Best Practices

Even with the best setup, issues can arise. Here are general tips and architectural considerations.

DNS Propagation Checks

After any DNS changes (especially with self-hosted DNS), always verify propagation.


# Check your authoritative server directly (from local machine)
dig @your-vps-ip your-domain.net A

# Check global propagation (from local machine)
dig your-domain.net A
# Use online tools like whatsmydns.net or dnschecker.org for visual propagation check.
                

Remember that DNS propagation can take 24-48 hours, though it's often much faster for A records.

Nginx/Apache Error Logs

When your website isn't loading, check the web server's error logs first. They provide crucial clues.


sudo tail -f /var/log/nginx/error.log # For Nginx errors
sudo tail -f /var/log/apache2/error.log # For Apache errors (if used)
sudo journalctl -u nginx.service -f # For Nginx service logs
sudo journalctl -u named.service -f # For Bind9 service logs
                

The "Nuke and Pave" Strategy

When a control panel (like TinyCP) or complex configuration becomes unmanageable or corrupted, a clean operating system reinstall ("nuke and pave") is often the most efficient path to a stable system. Always back up your data first!

Standard vs. Custom Directories

**Recommendation:** Stick to standard directory structures like `/var/www/your-domain.net/html`. While custom paths offer a minor (and easily bypassed) "security by obscurity," they significantly increase complexity, lead to permission issues, and hinder troubleshooting and tool compatibility. Focus on robust permissions, regular updates, and strong security practices instead.

Nginx vs. Apache vs. Nginx+Apache

Choosing your web server architecture impacts performance, flexibility, and maintenance.

Given our journey, **Nginx alone with PHP-FPM is generally the most efficient and manageable choice for a VPS.**