May 12, 2026
Self-hosting Gitea on AWS with Terraform
How I provisioned an AWS EC2 instance with Terraform, configured SSH access, installed Gitea, connected MariaDB, and put Nginx in front as a reverse proxy.
Quick navigation
- Overview
- Infrastructure with Terraform
- SSH key setup
- Database setup
- Gitea systemd service
- Nginx reverse proxy
- Common issues I hit
- Final result
Repository: github.com/kaungmyathan18/devops-private-git-server
Overview
I recently set up a private Git server using Gitea on an AWS EC2 instance. The stack is simple:
- AWS EC2 running Amazon Linux 2023
- Terraform for infrastructure provisioning
- MariaDB as the Gitea database
- Nginx as a reverse proxy
- Gitea running on port
3000 - Public access through port
80
The final flow looks like this:
Browser -> Nginx :80 -> Gitea :3000 -> MariaDB
Infrastructure with Terraform
The Terraform project provisions a VPC, subnets, security group, EC2 instance, and an AWS key pair.
The EC2 instance uses Amazon Linux 2023:
data "aws_ami" "amazon_linux_2023" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-2023.*-x86_64"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
I originally tried t2.micro, but AWS rejected it because it was not Free Tier eligible for my account. I switched to:
instance_type = "t3.micro"
SSH key setup
Instead of manually creating an EC2 key pair in the AWS Console, Terraform creates one from my local public SSH key:
create_key_pair = true
key_name = "kmh-admin"
public_key_path = "~/.ssh/id_ed25519.pub"
Only the public key is used. The private key stays local.
To connect:
ssh -i ~/.ssh/id_ed25519 ec2-user@YOUR_PUBLIC_IP
Database setup
Gitea uses MariaDB. The database setup script looks like this:
CREATE DATABASE `gitea` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'gitea'@'localhost' IDENTIFIED BY 'gitea';
GRANT ALL PRIVILEGES ON `gitea`.* TO 'gitea'@'localhost';
FLUSH PRIVILEGES;
EXIT;
One mistake I made was using:
GRANT ALL PRIVILEGES ON DATABASE gitea.* TO gitea@localhost;
MariaDB does not use ON DATABASE in that syntax. The correct form is:
GRANT ALL PRIVILEGES ON `gitea`.* TO 'gitea'@'localhost';
Gitea systemd service
I created a systemd service for Gitea:
[Unit]
Description=Gitea Self-Hosted Git Server
After=network.target mariadb.service
[Service]
User=gitea
Group=gitea
ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini
Restart=always
WorkingDirectory=/var/lib/gitea
Environment=USER=gitea HOME=/var/lib/gitea
[Install]
WantedBy=multi-user.target
The file must live here:
/etc/systemd/system/gitea.service
Then enable and start it:
sudo systemctl daemon-reload
sudo systemctl enable --now gitea
sudo systemctl status gitea
A small issue I hit: I wrote Execstart instead of ExecStart. Systemd is case-sensitive, so the service failed until I fixed the capitalization.
Nginx reverse proxy
Gitea listens on port 3000, but users should access it through normal HTTP on port 80.
The Nginx config:
server {
listen 80;
server_name _;
client_max_body_size 512M;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
}
On Amazon Linux, I placed it at:
/etc/nginx/conf.d/gitea.conf
Then tested and reloaded Nginx:
sudo nginx -t
sudo systemctl enable --now nginx
sudo systemctl reload nginx
Common issues I hit
AWS credentials expired
Terraform failed with:
ExpiredToken: The security token included in the request is expired
The fix was to log in again and export credentials:
aws login
eval "$(aws configure export-credentials --format env)"
terraform apply
SSH was hanging
SSH timed out because the security group initially allowed port 22 only from inside the VPC.
I fixed it by allowing my public IP:
allowed_ssh_cidr_blocks = ["YOUR_PUBLIC_IP/32"]
Port 80 was timing out
Nginx was running, but AWS security group rules did not allow public HTTP traffic.
I opened HTTP and HTTPS publicly:
allowed_service_cidr_blocks = ["0.0.0.0/0"]
I kept application ports private by separating them:
allowed_application_cidr_blocks = ["10.0.0.0/16"]
Gitea redirected to the wrong URL
When clicking register, Gitea redirected to something like:
http://user/sign_up
The issue was a bad ROOT_URL and incorrect domain values in /etc/gitea/app.ini.
Bad config:
DOMAIN = http://44.203.218.46/
SSH_DOMAIN = http://44.203.218.46/
ROOT_URL = http://44.203.218.46//
Correct config:
DOMAIN = 44.203.218.46
SSH_DOMAIN = 44.203.218.46
ROOT_URL = http://44.203.218.46/
After updating the config:
sudo systemctl restart gitea
sudo systemctl reload nginx
Final result
After the fixes, Gitea was reachable through Nginx:
http://YOUR_PUBLIC_IP
The setup now has:
- Terraform-managed AWS infrastructure
- EC2 key pair created from a local SSH public key
- MariaDB-backed Gitea
- Gitea managed by systemd
- Nginx reverse proxy on port
80 - Correct
ROOT_URLconfiguration for web redirects
This is a simple but solid starting point for a self-hosted private Git server. Terraform and configs on GitHub