When deploying YugabyteDB and YugabyteDB Anywhere (YBA) in AWS, one of the first hurdles is networking.
The YugabyteDB official documentation lays out a long list of ports that need to be opened for node-to-node traffic, YBA-to-node communication, app connections, and admin access.
Doing this manually through the AWS console is painful. Even with aws ec2 authorize-security-group-ingress, it’s error-prone if you try to keep the rules straight across environments. That’s where Ansible comes in.
The Port Matrix
Here’s a quick view of the key ports YugabyteDB requires:
• Node ↔ Node (intra-cluster):
7000, 7100, 9000, 9100, 18018• YBA ↔ DB Nodes:
5433, 9042, 9070, 9300, 12000, 13000(and optionally SSH/22 for legacy provisioning)• Applications ↔ DB Nodes:
5433 (YSQL), 9042 (YCQL)• YBA UI / Agents:
443 (YBA UI + agent callbacks), 9090 (Prometheus)
For xCluster replication, make sure 7100 and 9100 are open both ways between universes (9000 optional but recommended for lag metrics).
Why Split Security Groups?
It’s cleaner and safer to use two distinct security groups:
• DB-node SG: Opens only what DB nodes need for replication, YBA provisioning, and application access.
• YBA SG: Exposes the UI and metrics (443/9090) to operators, and allows DB-node agents to call back to YBA on 443.
This separation limits the blast radius and makes audits much easier.
The Automation Approach
Rather than hand-crafting rules, we can write an Ansible playbook that loops through the ports and applies them consistently. The playbook uses the AWS CLI’s authorize-security-group-ingress command, and can work in two modes:
CIDR-based rules – simple, works across VPCs.
Source-SG rules – tighter, allows DB SG ↔ YBA SG directly without exposing wide CIDRs.
We also make it idempotent by ignoring duplicate-rule errors, so the play can be run repeatedly.
The Full Playbook
---
# open-yugabyte-ports-split-sg.yml
#
# Usage example:
# ansible-playbook open-yugabyte-ports-split-sg.yml \
# -e aws_region=us-east-1 \
# -e aws_profile=default \
# -e sg_db_id=sg-0db123456789 \
# -e sg_yba_id=sg-0ab987654321 \
# -e use_source_groups=true \
# -e db_cidrs='["10.0.0.0/16"]' \
# -e yba_cidrs='["10.1.0.0/16"]' \
# -e app_cidrs='["10.2.0.0/16"]' \
# -e admin_cidrs='["203.0.113.0/24"]'
- name: Open YBA/YugabyteDB ports via AWS CLI (split: DB SG vs YBA SG)
hosts: localhost
connection: local
gather_facts: false
vars:
sg_db_id: ""
sg_yba_id: ""
sg_db_name: ""
sg_yba_name: ""
vpc_id: ""
aws_region: us-east-1
aws_profile: default
use_source_groups: false
db_cidrs: ["10.0.0.0/16"]
yba_cidrs: ["10.1.0.0/16"]
app_cidrs: ["10.2.0.0/16"]
admin_cidrs: ["203.0.113.0/24"]
node_to_node_ports: [7000, 7100, 9000, 9100, 18018]
yba_to_db_ports: [22, 5433, 7000, 7100, 9000, 9100, 9042, 9070, 9300, 12000, 13000, 18018]
app_to_db_ports: [5433, 9042]
yba_ui_ports_for_admins: [443, 9090]
yba_agent_port: 443
tasks:
- name: Ensure AWS CLI is available
command: aws --version
register: awscli_check
failed_when: awscli_check.rc != 0
changed_when: false
- name: Resolve SG IDs by name if needed (DB SG)
when: sg_db_id | length == 0
command: >
aws ec2 describe-security-groups
--region {{ aws_region }} --profile {{ aws_profile }}
--filters Name=group-name,Values={{ sg_db_name }} Name=vpc-id,Values={{ vpc_id }}
register: db_sg_lookup
changed_when: false
- set_fact:
sg_db_id: "{{ (db_sg_lookup.stdout | from_json).SecurityGroups[0].GroupId }}"
when: sg_db_id | length == 0
- name: Resolve SG IDs by name if needed (YBA SG)
when: sg_yba_id | length == 0
command: >
aws ec2 describe-security-groups
--region {{ aws_region }} --profile {{ aws_profile }}
--filters Name=group-name,Values={{ sg_yba_name }} Name=vpc-id,Values={{ vpc_id }}
register: yba_sg_lookup
changed_when: false
- set_fact:
sg_yba_id: "{{ (yba_sg_lookup.stdout | from_json).SecurityGroups[0].GroupId }}"
when: sg_yba_id | length == 0
- debug: msg="DB SG={{ sg_db_id }}, YBA SG={{ sg_yba_id }}"
# --- DB SG Rules ---
- name: DB SG | Node↔Node (CIDRs)
when: not use_source_groups
vars:
pairs: "{{ query('product', node_to_node_ports, db_cidrs) }}"
loop: "{{ pairs }}"
command: >
aws ec2 authorize-security-group-ingress
--region {{ aws_region }} --profile {{ aws_profile }}
--group-id {{ sg_db_id }}
--protocol tcp --port {{ item.0 }} --cidr {{ item.1 }}
register: db_node_node
failed_when: db_node_node.rc != 0 and
("InvalidPermission.Duplicate" not in db_node_node.stderr)
- name: DB SG | Node↔Node (SG reference)
when: use_source_groups
loop: "{{ node_to_node_ports }}"
command: >
aws ec2 authorize-security-group-ingress
--region {{ aws_region }} --profile {{ aws_profile }}
--group-id {{ sg_db_id }}
--protocol tcp --port {{ item }}
--source-group {{ sg_db_id }}
register: db_node_node_sg
failed_when: db_node_node_sg.rc != 0 and
("InvalidPermission.Duplicate" not in db_node_node_sg.stderr)
- name: DB SG | YBA→DB (CIDRs)
when: not use_source_groups
vars:
pairs: "{{ query('product', yba_to_db_ports, yba_cidrs) }}"
loop: "{{ pairs }}"
command: >
aws ec2 authorize-security-group-ingress
--region {{ aws_region }} --profile {{ aws_profile }}
--group-id {{ sg_db_id }}
--protocol tcp --port {{ item.0 }} --cidr {{ item.1 }}
register: db_yba_to_db
failed_when: db_yba_to_db.rc != 0 and
("InvalidPermission.Duplicate" not in db_yba_to_db.stderr)
- name: DB SG | YBA→DB (SG reference)
when: use_source_groups
loop: "{{ yba_to_db_ports }}"
command: >
aws ec2 authorize-security-group-ingress
--region {{ aws_region }} --profile {{ aws_profile }}
--group-id {{ sg_db_id }}
--protocol tcp --port {{ item }}
--source-group {{ sg_yba_id }}
register: db_yba_to_db_sg
failed_when: db_yba_to_db_sg.rc != 0 and
("InvalidPermission.Duplicate" not in db_yba_to_db_sg.stderr)
- name: DB SG | Apps→DB (CIDRs only)
vars:
pairs: "{{ query('product', app_to_db_ports, app_cidrs) }}"
loop: "{{ pairs }}"
command: >
aws ec2 authorize-security-group-ingress
--region {{ aws_region }} --profile {{ aws_profile }}
--group-id {{ sg_db_id }}
--protocol tcp --port {{ item.0 }} --cidr {{ item.1 }}
register: db_app_to_db
failed_when: db_app_to_db.rc != 0 and
("InvalidPermission.Duplicate" not in db_app_to_db.stderr)
# --- YBA SG Rules ---
- name: YBA SG | DB agents→YBA (CIDRs)
when: not use_source_groups
loop: "{{ db_cidrs }}"
command: >
aws ec2 authorize-security-group-ingress
--region {{ aws_region }} --profile {{ aws_profile }}
--group-id {{ sg_yba_id }}
--protocol tcp --port {{ yba_agent_port }} --cidr {{ item }}
register: yba_agents_cidr
failed_when: yba_agents_cidr.rc != 0 and
("InvalidPermission.Duplicate" not in yba_agents_cidr.stderr)
- name: YBA SG | DB agents→YBA (SG reference)
when: use_source_groups
command: >
aws ec2 authorize-security-group-ingress
--region {{ aws_region }} --profile {{ aws_profile }}
--group-id {{ sg_yba_id }}
--protocol tcp --port {{ yba_agent_port }}
--source-group {{ sg_db_id }}
register: yba_agents_sg
failed_when: yba_agents_sg.rc != 0 and
("InvalidPermission.Duplicate" not in yba_agents_sg.stderr)
- name: YBA SG | Admins→YBA UI/metrics
vars:
pairs: "{{ query('product', yba_ui_ports_for_admins, admin_cidrs) }}"
loop: "{{ pairs }}"
command: >
aws ec2 authorize-security-group-ingress
--region {{ aws_region }} --profile {{ aws_profile }}
--group-id {{ sg_yba_id }}
--protocol tcp --port {{ item.0 }} --cidr {{ item.1 }}
register: yba_admins
failed_when: yba_admins.rc != 0 and
("InvalidPermission.Duplicate" not in yba_admins.stderr)
Must-set variables (pick one path for each)
1) Which Security Groups to modify
Choose one method:
A. Direct by IDs (simplest)
•
sg_db_id— Security Group ID for DB nodes (e.g.,sg-0db123...)•
sg_yba_id— Security Group ID for YBA VM (e.g.,sg-0ab987...)
B. Lookup by names (if you don’t know IDs)
•
sg_db_name— name of DB SG (e.g.,yb-db-sg)•
sg_yba_name— name of YBA SG (e.g.,yb-yba-sg)•
vpc_id— VPC where those SGs live (e.g.,vpc-0123...)
You only need A or B. If you set IDs, you can leave the names/VPC blank.
2) How to scope access
Pick one:
• CIDR mode (cross-VPC friendly)
◦ Set
use_source_groups: false◦ Set CIDR lists below
• SG-to-SG mode (tighter, same VPC)
◦ Set
use_source_groups: true◦ CIDRs are ignored for DB↔YBA flows; you’ll still use CIDRs for apps and admin access
3) Your networks / CIDRs
(Used when use_source_groups: false; also always used for apps/admins)
•
db_cidrs— CIDRs where DB nodes live (often the DB subnets)•
yba_cidrs— CIDRs where YBA lives•
app_cidrs— CIDRs where application clients live•
admin_cidrs— CIDRs for operators who reach YBA UI/metrics
Examples:
db_cidrs: ["10.0.0.0/16"]
yba_cidrs: ["10.1.0.0/16"]
app_cidrs: ["10.2.0.0/16"]
admin_cidrs: ["203.0.113.0/24"]
4) AWS CLI context
•
aws_region— region of your SGs (e.g.,us-east-1)•
aws_profile— local AWS CLI profile to use (e.g.,default)◦ Ensure the profile has permission for:
ec2:AuthorizeSecurityGroupIngress,ec2:DescribeSecurityGroups
Optional variables (tweak if needed)
•
node_to_node_ports,yba_to_db_ports,app_to_db_ports,yba_ui_ports_for_admins,yba_agent_port
Only change if you’ve customized YSQL/YCQL or other ports in your environment.• Keep
22inyba_to_db_portsonly if you still use legacy provisioning; otherwise remove it.• If you also want to revoke unknown rules, ask me for the “enforce exact state” companion play.
Two example command lines
1) IDs + SG-to-SG (tightest)
ansible-playbook open-yugabyte-ports-split-sg.yml \
-e aws_region=us-east-1 -e aws_profile=default \
-e sg_db_id=sg-0db123456789 \
-e sg_yba_id=sg-0ab987654321 \
-e use_source_groups=true \
-e admin_cidrs='["203.0.113.0/24"]' \
-e app_cidrs='["10.2.0.0/16"]'
(DB↔DB and YBA↔DB are scoped SG→SG; admins/apps still use CIDRs.)
2) Names + VPC + CIDRs (cross-VPC)
ansible-playbook open-yugabyte-ports-split-sg.yml \
-e aws_region=us-east-1 -e aws_profile=default \
-e sg_db_name=yb-db-sg -e sg_yba_name=yb-yba-sg -e vpc_id=vpc-0123456789abcdef0 \
-e use_source_groups=false \
-e db_cidrs='["10.0.0.0/16"]' \
-e yba_cidrs='["10.1.0.0/16"]' \
-e app_cidrs='["10.2.0.0/16"]' \
-e admin_cidrs='["203.0.113.0/24"]'
Quick preflight checklist
✅ AWS CLI installed and credentials/profile working (
aws sts get-caller-identity)✅ The SGs exist in the specified region/VPC
✅ CIDRs reflect real routes/peering between VPCs/regions
✅ Remove
22if you’re not using legacy provisioning✅ If you customized YSQL/YCQL ports, update the port lists accordingly
Benefits of Ansible + AWS CLI
• Repeatable: Rerun safely, rules won’t duplicate.
• Environment-aware: Plug in CIDRs or SG names per environment.
• More secure: Use SG-to-SG references instead of broad CIDRs when possible.
• Audit-friendly: Everything’s codified in one place.
Pro Tips
• Don’t open SSH/22 unless you’re using legacy provisioning.
• For hardened AMIs (e.g., CIS RHEL), mirror rules in
firewalldif required.• For multi-region universes, add peered VPC CIDRs or use SG-to-SG rules.
Closing thoughts
With this automation in place, you’ll never again wonder “did I forget 9100 or 9300?” The playbook ensures that your YugabyteDB and YBA environments always have the correct networking configuration… consistent, secure, and easy to audit.
