OCI gives you two virtual firewalls. Both can be used at once, and traffic is allowed if either allows it. Understanding that "union" rule is the key to most OCI connectivity puzzles.
Side by side
| Security list | Network security group (NSG) | |
|---|---|---|
| Applies to | Every VNIC in the subnet | VNICs you add to it (instances, LBs, DBs) |
| Up to | Several per subnet | Several per VNIC |
| Source/destination | CIDR, service CIDR | CIDR, service, or another NSG |
| Stateful | Per rule (default stateful) | Per rule (default stateful) |
| Best for | A broad subnet baseline | Per-application rules |
Both are allow-only. When a packet reaches a VNIC, OCI checks the union of all security lists on the subnet and all NSGs on the VNIC; if any rule allows it, it passes.
Tip
Oracle recommends NSGs for application rules because they follow the resource and can reference each other ("app NSG may talk to db NSG on 1521"), not IP ranges.
The default security list
A new VCN created with the wizard gets a default security list that typically allows:
- Ingress TCP 22 from
0.0.0.0/0. - Ingress ICMP type 3 code 4 (path MTU) from anywhere, and ICMP type 3 from the VCN.
- Egress all.
Remove the open SSH rule for production and use the Bastion service instead.
Create an NSG pair for a web and database tier
WEB_NSG=$(oci network nsg create --compartment-id $COMP --vcn-id $VCN --display-name nsg-web --query 'data.id' --raw-output)
DB_NSG=$(oci network nsg create --compartment-id $COMP --vcn-id $VCN --display-name nsg-db --query 'data.id' --raw-output)
cat > db-rules.json <<'EOF'
[{"direction":"INGRESS","protocol":"6","source":"NSG_WEB","sourceType":"NETWORK_SECURITY_GROUP",
"tcpOptions":{"destinationPortRange":{"min":1521,"max":1521}},"isStateless":false,"description":"web to db"}]
EOF
sed -i "s|NSG_WEB|$WEB_NSG|" db-rules.json
oci network nsg rules add --nsg-id $DB_NSG --security-rules file://db-rules.json
Protocol numbers: 6 TCP, 17 UDP, 1 ICMP, all.
Add an instance's VNIC to the NSG:
VNIC=$(oci compute instance list-vnics --instance-id $INSTANCE --query 'data[0].id' --raw-output)
oci network vnic update --vnic-id $VNIC --nsg-ids "[\"$WEB_NSG\"]"
--nsg-ids replaces the list, so include any NSGs the VNIC already had.
Stateless rules and replies
Rules are stateful by default: replies are allowed automatically. A stateless rule (sometimes used for high-traffic load balancer subnets) needs a matching rule in the other direction for the return traffic, including ephemeral ports. Mixing them up gives "connects, then hangs" symptoms.
Checklist: I cannot reach my instance
- Instance running, and the service listening? Use the serial console or Run Command if you cannot SSH:
ss -tlnp | grep ':22\|:8080'
- OS firewall. Oracle Linux images ship with
firewalldenabled; open the port there too:
sudo firewall-cmd --permanent --add-port=8080/tcp && sudo firewall-cmd --reload
- Security lists on the subnet allow ingress on the port from your source.
- NSGs on the VNIC (if the security list does not) allow it.
- Route table: public subnet routes
0.0.0.0/0to an internet gateway; private subnet to a NAT gateway or DRG. - Public IP assigned, if you connect from the internet.
- Your side: corporate firewall or VPN blocking the port.
Check rules from the CLI:
oci network security-list list --compartment-id $COMP --vcn-id $VCN \
--query 'data[].{name:"display-name", ingress:"ingress-security-rules"[].{src:source, proto:protocol, port:"tcp-options"."destination-port-range".min}}'
oci network nsg rules list --nsg-id $WEB_NSG --output table
Network path analysis in Network Command Center > Network Path Analyzer traces a flow and names the rule or route that blocks it.
Symptom map
| Symptom | Usually |
|---|---|
| Timeout | Security list / NSG / route / OS firewall |
| Connection refused | Reached the host; nothing listening |
| Works by IP from inside VCN, not from internet | No public IP, no IGW route, or rule only allows VCN CIDR |
| SSH works, app port does not | firewalld on the instance |
Prevent it next time
- Keep the subnet security list minimal (ICMP, maybe Bastion), put application rules in NSGs.
- Name NSGs by role; reference NSGs instead of IP ranges.
- Enable VCN flow logs on production subnets so you can see dropped traffic.