This Terraform project creates a secure bastion host in AWS to manage SSH access to a private EC2 instance. The bastion is the only public entry point; private instances remain inaccessible from the internet.
- VPC with a public subnet (10.0.1.0/24) and a private subnet (10.0.2.0/24)
- Bastion Host: public EC2 with Elastic IP, SSH access restricted to your IP, Fail2Ban installed
- Private Server: EC2 in private subnet, no public IP, SSH allowed only from the Bastion
- SSH keys are generated automatically by Terraform and stored locally
bastion-host-setup/
- main.tf # VPC, subnets, IGW, NAT, routing
- bastion.tf # Bastion EC2, SG, EIP, user_data (Fail2Ban)
- private-server.tf # Private EC2 and SG
- keys.tf # Automatic SSH key generation
- iam.tf # Optional: IAM roles/policies (e.g., CloudWatch)
- variables.tf # Configurable variables
- outputs.tf # Useful outputs (bastion IP, private IPs, key paths)
- terraform.tfvars # Environment-specific values
- Terraform (>= 0.12 recommended)
- AWS account and credentials configured (AWS CLI or env vars)
- Internet access to download providers and AMIs
-
Initialize Terraform terraform init
-
Validate config terraform validate
-
Preview plan terraform plan -out infra.plan
-
Apply terraform apply infra.plan
- VPC, public and private subnets, IGW and NAT
- Security groups:
- Bastion SG: SSH (22) from your local IP only
- Private SG: SSH (22) from Bastion private IP only
- Bastion EC2 (public) with Elastic IP and Fail2Ban via user_data
- Private EC2 (no public IP)
- Two PEM key files generated locally:
- bastion_key.pem
- private_server_key.pem
-
Protect generated keys: chmod 400 bastion_key.pem private_server_key.pem
-
SSH to bastion: ssh -i bastion_key.pem ubuntu@<bastion_public_ip>
-
From bastion, SSH to private instance: ssh -i ~/.ssh/private-server.pem ubuntu@<private_server_private_ip>
Recommended: copy the private-server key to the bastion first: scp -i bastion_key.pem private_server_key.pem ubuntu@<bastion_public_ip>:/home/ubuntu/.ssh/private-server.pem ssh -i bastion_key.pem ubuntu@<bastion_public_ip> chmod 400 ~/.ssh/private-server.pem ssh -i ~/.ssh/private-server.pem ubuntu@10.0.2.x
Fail2Ban is installed automatically on the bastion using instance user_data. To check status:
- sudo fail2ban-client status
- sudo systemctl status fail2ban
- sudo tail -f /var/log/fail2ban.log
- Bastion SSH ingress is limited to your IP (configure in terraform.tfvars)
- Private server SSH is limited to the Bastion private IP
- Keys are generated locally and not stored in the repository or AWS
- Rotate keys and restrict who has access to PEM files
- Consider additional hardening: MFA for AWS console, session logging (CloudWatch), and OS-level hardening
Expected useful outputs (from outputs.tf):
- bastion_public_ip
- bastion_private_ip
- private_server_private_ip
- local paths to generated PEM files
To destroy everything created by Terraform: terraform destroy
- If SSH times out, check security group rules and routing
- If instances fail to boot services, view cloud-init logs: sudo journalctl -u cloud-init -b /var/log/cloud-init-output.log
- Adjust AMI, instance type, and region in variables.tf
- This project is a starting point; adapt IAM, monitoring, and logging for production
https://roadmap.sh/projects/bastion-host
MIT (or specify your preferred license)