Terraform for AWS EC2 instance running NGINX as a public reverse proxy for private application servers.
By default this creates a minimal VPC, public subnet, security group, Elastic IP, and Amazon Linux 2023 ARM instance. You can also deploy into an existing VPC and subnet.
- Terraform 1.15.6 or newer
- AWS credentials configured locally
- A CIDR for SSH access, usually your public IP with
/32
cp terraform.tfvars.example terraform.tfvarsEdit terraform.tfvars:
aws_region = "us-east-1"
ssh_cidr = "1.1.1.1/32"Deploy:
terraform init
terraform plan
terraform apply
terraform outputproxy_public_ip is the Elastic IP by default. Set enable_elastic_ip = false if you do not want Terraform to allocate one.
Connect to the instance:
terraform output -raw generated_private_key_pem > nginx-proxy-ssh.pem
chmod 600 nginx-proxy-ssh.pem
ssh -i nginx-proxy-ssh.pem ec2-user@$(terraform output -raw proxy_public_ip)To use an existing AWS key pair instead of a generated key:
create_ssh_key = false
key_name = "your-aws-key-pair-name"To deploy into an existing public subnet:
create_vpc = false
vpc_id = "vpc-xxxxxxxx"
subnet_id = "subnet-xxxxxxxx"The subnet must have a route to an internet gateway if the proxy should receive public traffic.
Manual TLS is the default:
tls_mode = "manual"Upload your certificate and key after deploy:
scp cert.cert ec2-user@PROXY_PUBLIC_IP:/tmp/
scp cert.key ec2-user@PROXY_PUBLIC_IP:/tmp/
ssh ec2-user@PROXY_PUBLIC_IP
sudo mv /tmp/cert.cert /tmp/cert.key /etc/nginx/ssl/
sudo chmod 644 /etc/nginx/ssl/cert.cert
sudo chmod 600 /etc/nginx/ssl/cert.key
sudo nginx -t
sudo systemctl reload nginxConfigure the domain and email:
tls_mode = "letsencrypt"
domain_name = "app.example.com"
letsencrypt_email = "admin@example.com"Apply, point DNS at proxy_public_ip, then run issuance:
ssh ec2-user@PROXY_PUBLIC_IP
sudo /usr/local/bin/issue-letsencryptIf DNS already points to this instance before first boot, you can issue during bootstrap:
letsencrypt_auto_issue = trueUse staging while testing issuance:
letsencrypt_staging = trueExample configs live in app-examples/:
nginx.app.conf: standard HTTPS reverse proxynginx.websocket.conf: WebSocket reverse proxynginx.cached.conf: static asset caching at the proxy
Copy one to the proxy, edit placeholders, and enable it:
scp app-examples/nginx.app.conf ec2-user@PROXY_PUBLIC_IP:/tmp/app.conf
ssh ec2-user@PROXY_PUBLIC_IP
sudo cp /tmp/app.conf /etc/nginx/conf.d/app.conf
sudo nano /etc/nginx/conf.d/app.conf
sudo nginx -t
sudo systemctl reload nginxReplace:
DOMAIN_NAMEwith your domainBACKEND_PRIVATE_IPwith the private IP of your application serverBACKEND_PORTwith the application portSSL_CERTIFICATE_PATHwith/etc/nginx/ssl/cert.certfor manual TLS or/etc/letsencrypt/live/DOMAIN_NAME/fullchain.pemfor Let's EncryptSSL_CERTIFICATE_KEY_PATHwith/etc/nginx/ssl/cert.keyfor manual TLS or/etc/letsencrypt/live/DOMAIN_NAME/privkey.pemfor Let's Encrypt
For the backend instance security group, allow the application port from proxy_private_ip only.
ssh_cidrcannot be0.0.0.0/0; use your current IP with/32.- Generated SSH private keys are stored in Terraform state. Protect state access or use an existing AWS key pair.
- Elastic IPs can incur AWS charges when allocated and not associated with a running instance.
- HTTP and HTTPS are public by default through
http_cidrsandhttps_cidrs. - Backend application ports should not be open to the internet.
- Terraform state can contain infrastructure identifiers. Store it according to your team security requirements.
502 Bad Gateway:
sudo tail -f /var/log/nginx/error.log
curl http://BACKEND_PRIVATE_IP:BACKEND_PORTCheck that the backend service is running, the backend security group allows traffic from proxy_private_ip, and the NGINX config uses the correct private IP and port.
TLS errors:
sudo nginx -t
sudo certbot certificatesFor manual TLS, verify files exist in /etc/nginx/ssl/ and the private key is readable by root.
Destroy:
terraform destroyContribute! Please open an issue or submit a pull request.
