| ansible | ||
| .gitignore | ||
| .terraform.lock.hcl | ||
| ansible-inventory.tf | ||
| ansible-inventory.yml.tmpl | ||
| cloud-init-hetzner.yml | ||
| main.tf | ||
| output.tf | ||
| README.md | ||
Hetzner Cloud Infrastructure
A comprehensive infrastructure-as-code project that provisions and configures a secure Ubuntu server on Hetzner Cloud using Terraform and Ansible. This setup provides a hardened, production-ready server with container support and security best practices.
🏗️ Architecture Overview
This project uses a two-stage approach:
-
Terraform/OpenTofu: Provisions the cloud infrastructure
- Creates a Hetzner Cloud server with data volume
- Configures SSH keys and initial cloud-init setup
- Generates Ansible inventory automatically
-
Ansible: Configures and hardens the server
- Updates system packages
- Hardens SSH configuration
- Sets up firewall rules (UFW)
- Mounts data volume
- Installs Docker (container runtime)
- Applies security hardening measures
🔧 Features
- Security Hardening: SSH hardening, firewall configuration, fail2ban setup
- Container Ready: Docker installation for containerized applications
- Data Persistence: Separate data volume for application data
- Automated Setup: Complete infrastructure provisioning and configuration
- Best Practices: Follows security and operational best practices
📋 Prerequisites
Before you begin, ensure you have:
- SSH Key Pair: Public and private keys (default expects
~/.ssh/HetznerPersonal.pub) - Hetzner Cloud API Token: Valid API token with appropriate permissions
- Terraform/OpenTofu: Version-compatible with the configuration
- Ansible: Latest version with required collections
- Required Ansible Collections:
ansible-galaxy collection install community.general ansible.posix community.docker
⚠️ Important: Deployment should only be attempted from Luke's machine for the moment.
🚀 Quick Start
1. Configure Terraform Variables
Create a secret.tfvars file with your Hetzner Cloud API token:
hcloud_token = "your-hetzner-cloud-api-token-here"
2. Provision Infrastructure
# Initialize Terraform/OpenTofu
tofu init
# Review the planned changes
tofu plan -var-file=./secret.tfvars
# Apply the configuration
tofu apply -var-file=./secret.tfvars
This will:
- Create a Hetzner Cloud server with data volume
- Generate an Ansible inventory file
- Output the server's IP address
3. Configure the Server
cd ansible
# Set a password for the administrative user
# This password will be used by Ansible for privilege escalation
ssh root@$(tofu -chdir=../ output -raw server_ip) passwd user
# Export the vault password
export VAULT_PW_ARGUMENT="--vault-password-file .vault_password.txt"
# Run the Ansible playbook to configure and harden the server
./run.sh
The Ansible playbook will:
- Update all system packages
- Harden SSH configuration (disable root login, change port)
- Configure UFW firewall (allow SSH, HTTP, HTTPS)
- Mount the data volume
- Install Docker container runtime
- Apply additional security hardening
- Reboot the server
📁 Project Structure
./
├── main.tf # Terraform main configuration
├── output.tf # Terraform outputs
├── ansible-inventory.tf # Generates Ansible inventory
├── ansible-inventory.yml.tmpl # Ansible inventory template
├── cloud-init-hetzner.yml # Initial server configuration
├── secret.tfvars # Your API token (create this)
└── ansible/
├── main.yml # Main Ansible playbook
├── inventory.yml # Generated inventory file
├── run.sh # Ansible execution script
├── vars/
│ └── variables.yml # Ansible variables
└── roles/
├── apt-update/ # Package updates
├── basic-firewall/ # UFW firewall setup
├── data-volume/ # Data volume mounting
├── docker/ # Docker installation
├── ssh-daemon/ # SSH hardening
└── restart-server/ # Server reboot
🔒 Security Features
-
SSH Hardening:
- Root login disabled
- Password authentication disabled
- SSH port changed from default
- Strong cryptographic algorithms enforced
- Session timeouts configured
-
Firewall Configuration:
- UFW enabled with deny-by-default policy
- SSH, HTTP, HTTPS ports allowed
- SSH brute force protection
-
System Hardening:
- Fail2ban configured for SSH protection
- Package updates applied
- Secure user account created with sudo privileges
🐳 Container Support
The server includes Docker for containerized applications:
- Docker installed from official Docker repository
- Includes Docker Compose for multi-container applications
- Data volume mounted for persistent storage
🔧 Customization
Modifying Server Configuration
Edit main.tf to change:
- Server instance type and location
- Volume size and configuration
- SSH key path
Modifying Ansible Configuration
Edit ansible/main.yml to:
- Enable/disable roles
- Add new configuration roles
- Modify execution order
Adding Secrets
Create ansible/secrets.yml.enc for encrypted variables:
ansible-vault create ansible/secrets.yml.enc
🚨 Important Notes
- SSH Port: The server uses a non-default SSH port (configured in cloud-init)
- Root Access: Root SSH access is permanently disabled after Ansible runs
- Data Volume: The data volume is mounted for persistent storage
- Firewall: UFW is configured to block all traffic except SSH, HTTP, and HTTPS
🔍 Troubleshooting
Common Issues
- Permission Denied: Verify your SSH key is correctly configured
- Ansible Connection Failed: Check that the server is fully provisioned and accessible
Useful Commands
# Check server status
tofu output server_ip
# Connect to server (use the correct SSH port from your configuration)
ssh -p <ssh_port> user@$(tofu output -raw server_ip)
# Check Ansible connectivity
cd ansible && ansible all -m ping -i inventory.yml
# Re-run specific Ansible roles
cd ansible && ansible-playbook main.yml --tags "firewall" -i inventory.yml
🧹 Cleanup
To destroy the infrastructure:
tofu destroy -var-file=./secret.tfvars
Warning: This will permanently delete your server and all data. Ensure you have backups of any important data stored on the server.
📝 License
This project is part of the Makerspace Leiden infrastructure.