• Home
  • Cloud VPS
    • Hong Kong VPS
    • US VPS
  • Dedicated Servers
    • Hong Kong Servers
    • US Servers
    • Singapore Servers
    • Japan Servers
  • Company
    • Contact Us
    • Blog
logo logo
  • Home
  • Cloud VPS
    • Hong Kong VPS
    • US VPS
  • Dedicated Servers
    • Hong Kong Servers
    • US Servers
    • Singapore Servers
    • Japan Servers
  • Company
    • Contact Us
    • Blog
ENEN
  • 简体简体
  • 繁體繁體
Client Area

Automate Hong Kong VPS Setup with Ansible: Server Provisioning Guide (2026)

August 30, 2026

Manually configuring a new VPS takes 2–4 hours and introduces human error. Ansible playbooks automate the entire process — installing packages, configuring Nginx, hardening SSH, setting up databases — in minutes, idempotently and repeatably. When you need a second Hong Kong VPS (for a warm standby, a new client environment, or a staging server), running one command reproduces your exact production configuration.


Why Ansible for VPS Automation?

  • Agentless — Ansible connects via SSH; no daemon to install on the VPS
  • Idempotent — run the same playbook ten times; only necessary changes are applied
  • Human-readable — YAML playbooks are documentation and automation in one file
  • No central server — run from your laptop or CI/CD pipeline; no Ansible master required

Step 1: Install Ansible on Your Local Machine

# macOS
brew install ansible

# Ubuntu/Debian
apt install -y ansible

# pip (any platform)
pip install ansible --break-system-packages

ansible --version  # Should show ansible [core 2.17+]

Step 2: Project Structure

mkdir -p ~/ansible-hk-vps/{roles,group_vars,host_vars}
cd ~/ansible-hk-vps

# inventory.ini — your VPS hosts
cat > inventory.ini << 'EOF'
[hk_vps]
hk-prod   ansible_host=YOUR_VPS_IP   ansible_user=root   ansible_port=22
hk-stage  ansible_host=YOUR_STAGE_IP ansible_user=root   ansible_port=22

[hk_vps:vars]
ansible_ssh_private_key_file=~/.ssh/id_ed25519
ansible_python_interpreter=/usr/bin/python3
EOF

# Test connectivity
ansible all -i inventory.ini -m ping

Step 3: Base Server Hardening Role

<code">mkdir -p roles/base/{tasks,handlers,templates,defaults}

cat > roles/base/defaults/main.yml << 'EOF' ssh_port: 22222 admin_user: deploy timezone: Asia/Hong_Kong swap_size_mb: 2048 EOF cat > roles/base/tasks/main.yml << 'EOF'
---
- name: Update apt cache and upgrade packages
  apt:
    update_cache: yes
    upgrade: dist
    cache_valid_time: 3600

- name: Install essential packages
  apt:
    name:
      - curl
      - wget
      - git
      - ufw
      - fail2ban
      - unattended-upgrades
      - htop
      - vim
      - ntp
      - logrotate
    state: present

- name: Set timezone
  timezone:
    name: "{{ timezone }}"

- name: Create swap file
  command: fallocate -l {{ swap_size_mb }}M /swapfile
  args:
    creates: /swapfile

- name: Set swap permissions
  file:
    path: /swapfile
    mode: '0600'

- name: Make swap
  command: mkswap /swapfile
  when: ansible_swaptotal_mb < 1

- name: Enable swap
  command: swapon /swapfile
  when: ansible_swaptotal_mb < 1 - name: Add swap to fstab lineinfile: path: /etc/fstab line: '/swapfile none swap sw 0 0' state: present - name: Create deploy user user: name: "{{ admin_user }}" shell: /bin/bash groups: sudo append: yes state: present - name: Add SSH key for deploy user authorized_key: user: "{{ admin_user }}" key: "{{ lookup('file', '~/.ssh/id_ed25519.pub') }}" state: present - name: Configure SSH hardening template: src: sshd_config.j2 dest: /etc/ssh/sshd_config validate: /usr/sbin/sshd -t -f %s notify: Restart SSH - name: Configure UFW defaults ufw: direction: "{{ item.direction }}" policy: "{{ item.policy }}" loop: - { direction: incoming, policy: deny } - { direction: outgoing, policy: allow } - name: Allow SSH on custom port ufw: rule: limit port: "{{ ssh_port }}" proto: tcp - name: Allow HTTP and HTTPS ufw: rule: allow port: "{{ item }}" proto: tcp loop: - '80' - '443' - name: Enable UFW ufw: state: enabled - name: Configure sysctl security settings sysctl: name: "{{ item.name }}" value: "{{ item.value }}" state: present reload: yes loop: - { name: net.ipv4.tcp_syncookies, value: '1' } - { name: net.ipv4.conf.all.rp_filter, value: '1' } - { name: kernel.randomize_va_space, value: '2' } - { name: fs.protected_symlinks, value: '1' } - { name: fs.protected_hardlinks, value: '1' } - { name: kernel.dmesg_restrict, value: '1' } EOF cat > roles/base/handlers/main.yml << 'EOF' --- - name: Restart SSH service: name: sshd state: restarted EOF cat > roles/base/templates/sshd_config.j2 << 'EOF'
Port {{ ssh_port }}
PasswordAuthentication no
PermitRootLogin prohibit-password
PubkeyAuthentication yes
MaxAuthTries 3
LoginGraceTime 30
X11Forwarding no
AllowAgentForwarding no
ClientAliveInterval 300
ClientAliveCountMax 3
EOF

Step 4: Nginx Role

<code">mkdir -p roles/nginx/{tasks,handlers,templates,defaults}

cat > roles/nginx/defaults/main.yml << 'EOF' domains: [] ssl_email: admin@yourdomain.com EOF cat > roles/nginx/tasks/main.yml << 'EOF' --- - name: Install Nginx and Certbot apt: name: - nginx - certbot - python3-certbot-nginx state: present - name: Enable and start Nginx service: name: nginx state: started enabled: yes - name: Deploy Nginx site configs template: src: site.conf.j2 dest: /etc/nginx/sites-available/{{ item.name }} loop: "{{ domains }}" notify: Reload Nginx - name: Enable site configs file: src: /etc/nginx/sites-available/{{ item.name }} dest: /etc/nginx/sites-enabled/{{ item.name }} state: link loop: "{{ domains }}" notify: Reload Nginx - name: Issue SSL certificates command: >
    certbot --nginx -d {{ item.domain }} --non-interactive
    --agree-tos --email {{ ssl_email }}
  loop: "{{ domains }}"
  args:
    creates: /etc/letsencrypt/live/{{ item.domain }}/fullchain.pem
EOF

cat > roles/nginx/handlers/main.yml << 'EOF'
---
- name: Reload Nginx
  service:
    name: nginx
    state: reloaded
EOF

Step 5: PostgreSQL Role

<code">mkdir -p roles/postgresql/{tasks,defaults}

cat > roles/postgresql/defaults/main.yml << 'EOF' postgresql_version: 16 postgresql_databases: [] postgresql_users: [] postgresql_shared_buffers: "256MB" postgresql_max_connections: 100 EOF cat > roles/postgresql/tasks/main.yml << 'EOF'
---
- name: Add PostgreSQL apt repository
  apt_repository:
    repo: deb http://apt.postgresql.org/pub/repos/apt {{ ansible_distribution_release }}-pgdg main
    state: present

- name: Install PostgreSQL
  apt:
    name: postgresql-{{ postgresql_version }}
    state: present

- name: Ensure PostgreSQL is started
  service:
    name: postgresql
    state: started
    enabled: yes

- name: Create PostgreSQL databases
  become_user: postgres
  postgresql_db:
    name: "{{ item.name }}"
    state: present
  loop: "{{ postgresql_databases }}"

- name: Create PostgreSQL users
  become_user: postgres
  postgresql_user:
    name: "{{ item.name }}"
    password: "{{ item.password }}"
    db: "{{ item.db }}"
    priv: ALL
    state: present
  loop: "{{ postgresql_users }}"
  no_log: true
EOF

Step 6: Master Playbook

<code">cat > site.yml << 'EOF' --- - name: Provision Hong Kong VPS hosts: hk_vps become: yes vars_files: - group_vars/all.yml roles: - base - nginx - postgresql post_tasks: - name: Verify services are running service: name: "{{ item }}" state: started loop: - nginx - postgresql - fail2ban - name: Print completion summary debug: msg: | ✅ VPS provisioning complete SSH port: {{ ssh_port }} Deploy user: {{ admin_user }} Nginx: running PostgreSQL: running EOF cat > group_vars/all.yml << 'EOF'
# Base configuration
ssh_port: 22222
admin_user: deploy
timezone: Asia/Hong_Kong

# Nginx
ssl_email: admin@yourdomain.com
domains:
  - name: myapp
    domain: yourdomain.com

# PostgreSQL
postgresql_databases:
  - name: myapp_production
postgresql_users:
  - name: myapp_user
    password: "{{ vault_db_password }}"
    db: myapp_production
EOF

Ansible Vault for Secrets

<code"># Never store passwords in plaintext — use Ansible Vault
ansible-vault create group_vars/vault.yml
# Add: vault_db_password: YourStrongPassword!

# Run playbook with vault
ansible-playbook -i inventory.ini site.yml --ask-vault-pass

# Or with a vault password file (for CI/CD)
echo "YourVaultPassword" > ~/.vault_pass
chmod 600 ~/.vault_pass
ansible-playbook -i inventory.ini site.yml --vault-password-file ~/.vault_pass

Step 7: Deploy Application Updates

<code">cat > deploy.yml << 'EOF'
---
- name: Deploy application update
  hosts: hk_vps
  become: yes
  vars:
    app_dir: /var/www/myapp
    app_user: deploy

  tasks:
    - name: Pull latest code
      git:
        repo: git@github.com:your-org/myapp.git
        dest: "{{ app_dir }}"
        version: main
        force: yes
      become_user: "{{ app_user }}"

    - name: Install dependencies
      command: npm install --production
      args:
        chdir: "{{ app_dir }}"
      become_user: "{{ app_user }}"

    - name: Run database migrations
      command: npm run migrate
      args:
        chdir: "{{ app_dir }}"
      become_user: "{{ app_user }}"

    - name: Restart application
      systemd:
        name: myapp
        state: restarted
        daemon_reload: yes

    - name: Verify application is healthy
      uri:
        url: http://127.0.0.1:3000/health
        status_code: 200
      retries: 5
      delay: 3
EOF

# Run deployment
ansible-playbook -i inventory.ini deploy.yml

Step 8: Provision Multiple VPS in Parallel

<code"># Ansible runs tasks on all hosts in parallel by default (forks=5)
# Provision 3 HK VPS simultaneously:

# inventory.ini
# [hk_vps]
# hk-prod-1  ansible_host=1.2.3.4
# hk-prod-2  ansible_host=1.2.3.5
# hk-staging ansible_host=1.2.3.6

# Run with 10 parallel forks
ansible-playbook -i inventory.ini site.yml -f 10

# Limit to specific host
ansible-playbook -i inventory.ini site.yml --limit hk-staging

# Check what would change (dry run)
ansible-playbook -i inventory.ini site.yml --check --diff

Conclusion

Ansible playbooks turn your Hong Kong VPS setup from a manual 3-hour process into a 10-minute automated run — idempotent, version-controlled, and repeatable. The roles cover base hardening (sysctl, SSH, UFW), Nginx with SSL, and PostgreSQL; additional roles for Redis, Node.js, Docker, or any other component follow the same pattern. With your playbook in Git, every new VPS is provisioned identically, and every configuration change is tracked and peer-reviewable.

Automate your infrastructure: Browse Server.HK Hong Kong VPS plans — provision a new VPS and run your Ansible playbook to have a production-ready server in under 15 minutes.

Leave a Reply

You must be logged in to post a comment.

Recent Posts

  • Self-Hosted Private Search Engine on Hong Kong VPS: SearXNG (2026)
  • WireGuard Site-to-Site VPN with Hong Kong VPS: Connect Your Offices (2026)
  • Deploy Elixir Phoenix on Hong Kong VPS: Real-Time Apps for Asia (2026)
  • GPU Dedicated Server in Hong Kong: AI Model Training and Inference (2026)
  • Vector Database on Hong Kong VPS: Build a RAG System with pgvector (2026)

Recent Comments

  1. Hong Kong VPS Uptime and SLA: What 99.9% Uptime Really Means for Your Business (2026) - Server.HK on How to Monitor Your Hong Kong VPS: Uptime, Performance, and Alert Setup Guide (2026)
  2. Best Hong Kong VPS Providers in 2026: Compared by Speed, Routing, and Value - Server.HK on How to Migrate Your Website to a Hong Kong VPS: Zero-Downtime Transfer Guide (2026)
  3. vibramycin injection on How to Choose the Right Hong Kong VPS Plan: A Buyer’s Guide for 2026
  4. allopurinol for gout on CN2 GIA vs BGP vs CN2 GT: What’s the Real Difference for China Connectivity?
  5. antibiotics online purchase on How to Set Up a WordPress Site on a Hong Kong VPS with aaPanel (Step-by-Step 2026)

Knowledge Base

Access detailed guides, tutorials, and resources.

Live Chat

Get instant help 24/7 from our support team.

Send Ticket

Our team typically responds within 10 minutes.

logo
Alipay Cc-paypal Cc-stripe Cc-visa Cc-mastercard Bitcoin
Cloud VPS
  • Hong Kong VPS
  • US VPS
Dedicated Servers
  • Hong Kong Servers
  • US Servers
  • Singapore Servers
  • Japan Servers
More
  • Contact Us
  • Blog
  • Legal
© 2026 Server.HK | Hosting Limited, Hong Kong | Company Registration No. 77008912
Telegram
Telegram @ServerHKBot