Security Lists vs NSGs in OCI — You’re Probably Using the Wrong One, and Here’s the Proof
Oracle ACE Apprentice Contribution | Category: Networking, Security & Architecture
Security Lists vs NSGs in OCI — You’re Probably Using the Wrong One, and Here’s the Proof
Oracle ACE Apprentice Contribution | Category: Networking, Security & Architecture
The Three Days I Lost to One Line of Documentation
A developer spent three full days debugging a ShadowSocks server on OCI. Security List said allow. NSG said deny. Traffic passed anyway. He consulted documentation, asked AI assistants, tried every combination of rules. Nothing made sense.
The answer was buried in one line in OCI’s networking docs that almost nobody reads before they start clicking:
“When mixing both NSG and security lists together, the most union of the two is enforced. This means that a packet is allowed if any rule in any of the relevant security lists or NSGs allows the traffic.”
OR logic. Not AND.
This is the most misunderstood behaviour in OCI networking. It has caused real security incidents. It has caused three-day debugging sessions. And it is the reason why adding an NSG with a narrow IP restriction does absolutely nothing if your Security List still has 0.0.0.0/0 on the same port.
This post explains what is actually happening in your VCN right now, why your NSG rules may be silently doing nothing, and how to fix it permanently.
The Real Problem: OR Logic Catches Everyone
Here is the exact scenario that catches developers:
You create a Security List with SSH open to the world during initial setup:
Security List Ingress: Source 0.0.0.0/0, TCP 22
Later, a security-conscious you creates an NSG and adds a tighter rule:
NSG Ingress: Source 1.2.3.4/32, TCP 22 ← your IP only
You assign the NSG to your instance’s VNIC and feel secure.
But your instance still accepts SSH from every IP on the internet. Because the Security List rule still exists. And OCI evaluates them with OR: if either allows it, the packet passes.
Even AI models get this wrong — describing OCI’s Security List and NSG relationship as AND rather than OR. Developers migrating from AWS assume the same AND behaviour between Security Groups and NACLs. They get it completely wrong on OCI.
The fix is not to add more NSG rules. The fix is to remove the broad Security List rule first.
# Find every rule open to the entire internet across all Security Lists
oci network security-list list \
--compartment-id "YOUR_COMPARTMENT_OCID" \
--vcn-id "YOUR_VCN_OCID" \
--all \
--query "data[].\"ingress-security-rules\"[]" \
| python3 -c "
import sys, json
rules = json.load(sys.stdin)
print('=== Rules open to entire internet ===')
for r in rules:
if r.get('source') == '0.0.0.0/0':
tcp = r.get('tcp-options', {})
port = tcp.get('destination-port-range', {})
print(f'Port {port.get(\"min\",\"any\")}-{port.get(\"max\",\"any\")} | Stateless: {r.get(\"is-stateless\")}')
"
Run this against your tenancy right now. What you find will be instructive.
Understanding the Two Tools — What They Actually Are
Security Lists are attached to subnets. Every rule in a Security List applies to every VNIC in that subnet — every Compute instance, every database, every load balancer, every resource you add in the future. You cannot make it apply to some resources and not others. The subnet is the hard boundary and everything inside inherits everything.
Network Security Groups are attached to individual VNICs. You explicitly add a VNIC to an NSG. Two instances in the same subnet can have completely different NSG memberships and therefore completely different effective security rules. A VNIC can belong to up to five NSGs simultaneously.
Oracle itself recommends using NSGs and has stated it will prioritise NSGs over Security Lists when implementing future enhancements. This is not a preference — it is a roadmap signal. Build on NSGs now.
Both Are Allow-Only — The Implicit Deny Everyone Forgets
This trips up developers coming from AWS and Azure before they understand anything else about OCI networking.
In AWS, Security Groups are allow-only — but Network ACLs support explicit deny rules. In Azure, NSGs support explicit deny rules with priority numbers. You can block a specific IP with a deny rule at priority 100 and everything else flows through.
OCI works differently. Neither Security Lists nor NSGs support explicit deny rules. Both are allow-only.
The security model is purely implicit deny: if no rule explicitly allows a packet, it is dropped. There is no way to write a rule in OCI that says “deny this specific IP” while allowing everything else.
This has real consequences:
You cannot block a specific attacker IP using OCI networking rules alone. If you have 0.0.0.0/0 TCP 22 in your Security List and want to block a specific bad actor at 5.6.7.8, there is no deny rule you can add. Your only options are to remove the broad 0.0.0.0/0 rule entirely and replace it with explicit allowed IPs, or block at the OS level:
# OCI has no deny rules — block attackers at OS level instead
sudo iptables -I INPUT -s 5.6.7.8 -j DROP
sudo iptables-save | sudo tee /etc/sysconfig/iptables
A fresh NSG with zero rules denies all traffic — both ingress and egress — to any VNIC assigned to it. This is why assigning a VNIC to an empty NSG immediately breaks connectivity. It is not a bug. It is the implicit deny working exactly as designed.
# Always verify an NSG has rules before assigning a VNIC to it
oci network nsg rules list \
--nsg-id "YOUR_NSG_OCID" \
--query "data[].[direction,description]" \
--output table
# If this returns empty — the NSG will block ALL traffic to that VNIC
# Add your allow rules BEFORE assigning
Critical implication for the OR logic problem: Because neither construct supports deny rules, you cannot use an NSG to cancel out a Security List permission. There is no NSG deny rule to override a Security List allow rule — deny rules simply do not exist in OCI. The only fix is to remove the Security List allow rule itself.
How CLI Updates Work — NSG vs Security List (Know This Before Touching Anything)
This difference has deleted production security configurations. Understand it before running any update command.
NSG Rules — Additive and Individually Manageable
NSG rules have their own OCIDs. You can add a single new rule, update one specific rule, or remove one specific rule without touching anything else in the NSG.
# Add a single new rule — all existing rules untouched
oci network nsg rules add \
--nsg-id "YOUR_NSG_OCID" \
--security-rules '[
{
"direction": "INGRESS",
"protocol": "6",
"source": "203.0.113.10/32",
"sourceType": "CIDR_BLOCK",
"description": "New developer laptop IP",
"isStateless": false,
"tcpOptions": {"destinationPortRange": {"min": 22, "max": 22}}
}
]'
# Get the OCID of a specific rule you want to update
oci network nsg rules list \
--nsg-id "YOUR_NSG_OCID" \
--query "data[].[id,description,direction]" \
--output table
# Update just that one rule by its OCID
oci network nsg rules update \
--nsg-id "YOUR_NSG_OCID" \
--security-rules '[
{
"id": "THE_SPECIFIC_RULE_OCID",
"source": "203.0.113.99/32",
"sourceType": "CIDR_BLOCK",
"description": "Updated developer IP",
"direction": "INGRESS",
"protocol": "6",
"isStateless": false,
"tcpOptions": {"destinationPortRange": {"min": 22, "max": 22}}
}
]'
# Remove one specific rule — nothing else is affected
oci network nsg rules remove \
--nsg-id "YOUR_NSG_OCID" \
--security-rule-ids '["THE_SPECIFIC_RULE_OCID"]'
NSG rules behave like database rows — each one has an identity and can be targeted individually.
Security List Rules — All or Nothing
Security List updates via CLI replace the entire ingress rule set or the entire egress rule set in a single atomic operation. There is no “add one rule” or “update one rule.” Every update is a full replacement of the complete list.
# THIS COMMAND REPLACES EVERY SINGLE INGRESS RULE
# Any existing rule not included in this list is permanently deleted
oci network security-list update \
--security-list-id "YOUR_SL_OCID" \
--ingress-security-rules '[
{"source": "0.0.0.0/0", "protocol": "1", "isStateless": false,
"icmpOptions": {"type": 3, "code": 4}}
]'
# Every other ingress rule that existed is now gone
The only safe workflow for any Security List update:
# Step 1 — Fetch and save all current rules first
oci network security-list get \
--security-list-id "YOUR_SL_OCID" \
--query "data.\"ingress-security-rules\"" > current_ingress_rules.json
cat current_ingress_rules.json # verify before touching anything
# Step 2 — Edit the saved file with your intended changes
# Add, modify, or remove rules in current_ingress_rules.json
# Step 3 — Update with the COMPLETE merged list
oci network security-list update \
--security-list-id "YOUR_SL_OCID" \
--ingress-security-rules "$(cat current_ingress_rules.json)" \
--force
Skip Step 1 and you risk wiping your entire production firewall rule set with one command.
The operational difference in plain terms:
Operation NSG Security List Add one new rule nsg rules add — others untouched Fetch all → append → update entire list Change one existing rule nsg rules update with rule OCID Fetch all → modify one → update entire list Delete one rule nsg rules remove with rule OCID Fetch all → remove one → update entire list Risk of accidental wipe None — operations are additive High — omitting any rule from update deletes it Rollback one bad change Remove the single rule you added Requires full rule set restore from backup
This operational difference alone is a strong reason to prefer NSGs for any environment where security rules change frequently. A partial CLI command, a copy-paste error, or a JSON syntax mistake in a Security List update silently wipes all your ingress rules. The same mistake in an NSG update simply fails to add the new rule — existing rules are never touched.
Where Security Lists Silently Break Your Security
Here is a concrete scenario that happens in real OCI environments.
Three instances in the same subnet — the OCI quickstart default:
web-server-01— public-facing Nginxapp-server-01— internal API applicationdb-server-01— Oracle database
Your Security List:
Rule 1: Source 0.0.0.0/0 TCP 443 (HTTPS — correct)
Rule 2: Source 0.0.0.0/0 TCP 22 (SSH — seemed fine at the time)
Rule 3: Source 10.0.0.0/8 TCP 1521 (DB access — intended internal only)
What you think Rule 2 allows: developers SSH into web-server-01.
What Rule 2 actually allows: anyone on the internet can attempt SSH connections to web-server-01, app-server-01, and db-server-01 — because the rule applies to the entire subnet.
What Rule 3 actually allows: any internal 10.x.x.x source to reach port 1521 — including web-server-01, which is in 10.x.x.x. A compromised web server can reach your database directly.
You cannot fix either of these problems by adding NSG rules. You cannot fix them by adding deny rules — deny rules do not exist. The only fix is to remove the broad Security List rules and replace them with NSG rules scoped to specific VNICs.
With NSGs this scenario is structurally impossible:
nsg-web: allows 443 from 0.0.0.0/0, allows 22 from developer IP only
nsg-app: allows 8080 from NSG:nsg-web only
nsg-db: allows 1521 from NSG:nsg-app only
The database never receives traffic from the web tier. A compromised web server cannot reach the database regardless of what subnet it is in — because the NSG rule for the database references nsg-app as the source, not a CIDR block.
The OR Logic vs AND Logic — How OCI Differs From AWS and Azure
Cloud Subnet-Level Control Instance-Level Control Logic When Both Present Deny Rules? AWS Network ACL (stateless) Security Group (stateful) AND — both must allow NACLs only Azure NSG at subnet (stateful) NSG at NIC (stateful) AND — most restrictive wins Yes, with priority OCI Security List (stateful/stateless) NSG (stateful) OR — either allowing is enough Neither
OCI’s OR behaviour is documented but consistently misunderstood. The consequence: you cannot use OCI networking rules to restrict access that is already permitted — you can only remove the permission. This is fundamentally different from AWS and Azure where a deny rule wins regardless of what else allows the traffic.
Where OCI NSG-as-source surpasses AWS and Azure:
OCI NSGs support referencing another NSG as a rule source or destination directly:
Allow TCP 1521 from source NSG:nsg-app
This means traffic is only permitted from VNICs that are explicitly members of nsg-app. Adding a new instance to the subnet does not give it database access — it must be explicitly added to nsg-app. The permission follows workload identity, not network location.
AWS achieves something similar by referencing Security Group IDs as sources, but it does not work cleanly across subnet boundaries. Azure’s Application Security Groups provide logical grouping but require an extra configuration layer. OCI’s NSG-to-NSG reference is clean, inline, and the recommended architecture for multi-tier applications.
The Three Real Problems You Are Likely Hitting Right Now
Problem 1 — NSG Rule Exists But Traffic Still Passes When It Should Not
Symptom: You added an NSG rule restricting a port but that port is still reachable from everywhere.
Cause: The Security List still has a broad rule allowing the same port. OR logic means the NSG restriction is completely bypassed. And because neither construct has deny rules, you cannot override the Security List from the NSG side.
# Find the conflicting Security List rule
oci network security-list list \
--compartment-id "YOUR_COMPARTMENT_OCID" \
--vcn-id "YOUR_VCN_OCID" \
--all \
--query "data[].\"ingress-security-rules\"[]" \
| python3 -c "
import sys, json
rules = json.load(sys.stdin)
for r in rules:
src = r.get('source','')
tcp = r.get('tcp-options',{})
port = tcp.get('destination-port-range',{})
print(f'Source: {src:25} Port: {port.get(\"min\",\"all\")}-{port.get(\"max\",\"all\")}')
"
The fix is to remove or narrow the Security List rule. Adding more NSG rules will not help.
Problem 2 — NSG Rule Added But Traffic Still Blocked
Symptom: You added a correct NSG allow rule but the connection still times out.
Cause A — VNIC not assigned to the NSG.
A new NSG with correct rules but no VNIC membership does nothing. The NSG rules only apply to VNICs explicitly added to it.
# Check which VNICs are actually in the NSG
oci network nsg vnics list \
--nsg-id "YOUR_NSG_OCID" \
--query "data[].[\"vnic-id\",\"resource-type\"]" \
--output table
# If empty — assign the VNIC
oci network vnic update \
--vnic-id "YOUR_VNIC_OCID" \
--nsg-ids '["YOUR_NSG_OCID"]'
Cause B — OS-level firewall blocking traffic OCI already allows.
Traffic is evaluated against Security List rules, NSG rules, and the OS firewall independently. OCI allowing a port and the instance accepting a connection are two different things. iptables or firewalld can block traffic that OCI's networking allows — both produce identical symptoms: connection timeout.
# Check iptables
sudo iptables -L INPUT -n --line-numbers
# Check firewalld
sudo firewall-cmd --list-all
# Open the port at OS level
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload
Problem 3 — East-West Traffic Between Instances in the Same Subnet Cannot Be Controlled
Symptom: Two instances in the same subnet can reach each other on any port even though you never explicitly allowed it.
Cause: Traffic between instances in the same subnet never crosses the subnet boundary. Security List rules apply at the subnet boundary. They cannot see or filter traffic that stays inside the subnet. This is an architectural limitation, not a configuration problem — and it cannot be fixed by adding or removing Security List rules.
NSGs fix this because rules apply at the VNIC level regardless of whether the other party is in the same subnet or a different one. If db-server-01's VNIC is in nsg-db and nsg-db only allows traffic from nsg-app, then web-server-01 in the same subnet cannot reach the database — because web-server-01 is not in nsg-app.
The Correct Architecture for a Three-Tier Application
Minimal Security List — only genuine subnet-wide infrastructure rules:
Security List Ingress:
Source 0.0.0.0/0 ICMP type 3 code 4 (path MTU discovery — always needed)
Source VCN CIDR ICMP type 8 (internal health check ping)
Security List Egress:
Destination 0.0.0.0/0 All Protocols (allow all outbound — refine per tier if needed)
Everything workload-specific in NSGs:
NSG: nsg-web
Ingress: 0.0.0.0/0 TCP 443 (HTTPS from internet)
Ingress: 0.0.0.0/0 TCP 80 (HTTP — redirect to HTTPS)
Ingress: YOUR_IP/32 TCP 22 (SSH — your IP only)
NSG: nsg-app
Ingress: NSG:nsg-web TCP 8080 (app traffic only from web tier)
NSG: nsg-db
Ingress: NSG:nsg-app TCP 1521 (DB access only from app tier)
(no SSH ingress at all — database is never directly accessible)
The web server cannot reach the database. A compromised web server cannot escalate regardless of subnet. Adding new instances to any subnet grants no automatic cross-tier access.
Migration Without Downtime — The Overlap Window Method
The OR logic that causes the problem also makes migration safe. While both Security List and NSG rules are active, traffic flows through whichever allows it. This overlap period is your safety net — at every step you can roll back by removing the NSG from the VNIC.
Phase 1 — Create NSGs and add rules (zero traffic impact)
# Create the three NSGs
oci network nsg create \
--compartment-id "YOUR_COMPARTMENT_OCID" \
--vcn-id "YOUR_VCN_OCID" \
--display-name "nsg-web" \
--freeform-tags '{"tier":"web","env":"prod"}'
oci network nsg create \
--compartment-id "YOUR_COMPARTMENT_OCID" \
--vcn-id "YOUR_VCN_OCID" \
--display-name "nsg-app" \
--freeform-tags '{"tier":"app","env":"prod"}'
oci network nsg create \
--compartment-id "YOUR_COMPARTMENT_OCID" \
--vcn-id "YOUR_VCN_OCID" \
--display-name "nsg-db" \
--freeform-tags '{"tier":"db","env":"prod"}'
# Add rules to nsg-web
oci network nsg rules add \
--nsg-id "NSG_WEB_OCID" \
--security-rules '[
{"direction":"INGRESS","protocol":"6","source":"0.0.0.0/0",
"sourceType":"CIDR_BLOCK","description":"HTTPS from internet",
"isStateless":false,"tcpOptions":{"destinationPortRange":{"min":443,"max":443}}},
{"direction":"INGRESS","protocol":"6","source":"YOUR_IP/32",
"sourceType":"CIDR_BLOCK","description":"SSH developer IP only",
"isStateless":false,"tcpOptions":{"destinationPortRange":{"min":22,"max":22}}}
]'
# Add rules to nsg-app — NSG as source, not CIDR
oci network nsg rules add \
--nsg-id "NSG_APP_OCID" \
--security-rules '[
{"direction":"INGRESS","protocol":"6",
"source":"NSG_WEB_OCID","sourceType":"NETWORK_SECURITY_GROUP",
"description":"App traffic only from web tier NSG",
"isStateless":false,"tcpOptions":{"destinationPortRange":{"min":8080,"max":8080}}}
]'
# Add rules to nsg-db — NSG as source
oci network nsg rules add \
--nsg-id "NSG_DB_OCID" \
--security-rules '[
{"direction":"INGRESS","protocol":"6",
"source":"NSG_APP_OCID","sourceType":"NETWORK_SECURITY_GROUP",
"description":"DB access only from app tier NSG",
"isStateless":false,"tcpOptions":{"destinationPortRange":{"min":1521,"max":1521}}}
]'
Phase 2 — Assign NSGs to VNICs (rules become active, Security List still active)
# Get instance VNICs
oci compute instance list-vnics \
--instance-id "YOUR_INSTANCE_OCID" \
--query "data[].[\"display-name\",id]" \
--output table
# Assign NSG — no reboot, takes effect immediately
oci network vnic update \
--vnic-id "YOUR_VNIC_OCID" \
--nsg-ids '["NSG_WEB_OCID"]'
Phase 3 — Test every traffic path before removing Security List rules
# Web tier should work
curl -v https://YOUR_WEB_SERVER_IP
# App tier from web should work
curl http://APP_SERVER_PRIVATE_IP:8080/health
# DB from web server should FAIL — this is the isolation test
nc -zv -w3 DB_SERVER_PRIVATE_IP 1521
# Expected: connection timed out
Phase 4 — Remove Security List rules for migrated traffic only after tests pass
Remember: Security List updates are all-or-nothing. Fetch current rules first.
# Step 1 — Save current ingress rules
oci network security-list get \
--security-list-id "YOUR_SL_OCID" \
--query "data.\"ingress-security-rules\"" > current_rules.json
# Step 2 — Edit current_rules.json, remove only the rules now covered by NSGs
# Keep ICMP rules and anything not yet migrated
# Step 3 — Update with the complete remaining list
oci network security-list update \
--security-list-id "YOUR_SL_OCID" \
--ingress-security-rules "$(cat current_rules.json)" \
--force
What’s Coming: OCI Zero Trust Packet Routing
OCI’s Zero Trust Packet Routing (ZPR) is a newer capability that builds on top of NSGs and Security Lists. Traffic is first evaluated against existing NSG rules, then by OCI ZPR policies. This helps ensure that only traffic that meets both the network security rules and the OCI ZPR policies is permitted.
This is AND logic — unlike the OR logic between Security Lists and NSGs. ZPR adds a mandatory additional layer that even a permissive Security List cannot bypass.
The advantages of ZPR over NSGs include that a security list or NSG cannot apply to more than one VCN, but a ZPR policy can apply to more than one VCN by adding security attributes to multiple VCNs.
ZPR uses intent-based policies written in plain language:
allow app:web endpoints to connect to app:database endpoints
When a new instance is added with the appropriate attributes, access is automatically applied, keeping security aligned with business logic, not IP addresses.
OCI ZPR is offered at no additional cost for OCI configuration and OCI activity across supported OCI services.
ZPR does not replace NSGs or Security Lists — it sits on top of them. This means: build good NSG architecture now, and you are positioned to add ZPR on top without restructuring anything.
Decision Framework
Situation Security List NSG All instances in subnet need identical rules ✅ Not needed Different instances in same subnet need different rules ❌ Cannot ✅ Restrict east-west traffic within a subnet ❌ Impossible ✅ Block a specific attacker IP ❌ No deny rules ❌ No deny rules — use iptables Restrict via rules when other rules already allow ❌ OR logic defeats this ❌ Must remove the other rule first Add one rule without touching others via CLI ❌ All or nothing ✅ Additive Update one specific rule via CLI ❌ Must rewrite entire list ✅ Update by rule OCID Tier-to-tier access control immune to subnet growth ❌ CIDR only ✅ NSG-as-source Production workload with compliance requirements ❌ Too coarse ✅ Foundation for ZPR (future-proofing) ❌ ✅ Required
Preventive Measures
Understand OR logic before adding any NSG rule. An NSG restriction is silently defeated by any Security List rule that already allows the same traffic. The fix is always to remove the Security List rule — not to add more NSG rules.
Never leave 0.0.0.0/0 on port 22 in a Security List. This was added for initial access and never cleaned up. It applies to every instance in the subnet including databases and application servers added months later.
Always fetch current Security List rules before updating. Security List updates via CLI are all-or-nothing. One missing rule in your update command permanently deletes that rule from your firewall. NSG updates are additive — existing rules are never affected by a new nsg rules add command.
Verify VNIC membership before debugging NSG rules. A correct NSG rule on an NSG with no VNIC members does nothing. Check membership first, always.
Remember the OS firewall as the third independent layer. OCI networking allowing a port and the OS accepting a connection are evaluated separately. Both produce identical symptoms when blocking — connection timeout. Check Security List, NSG, and OS firewall in that order.
Do not rely on NSG rules to restrict something a Security List already allows. Because there are no deny rules in either construct, NSG restrictions cannot override Security List permissions. The Security List must be narrowed first.
Common Mistakes
Mistake 1: Expecting NSG to restrict what a Security List allows The most dangerous and most common misconception. OCI uses OR logic — either allowing is enough. An NSG with a narrow IP restriction does nothing if the Security List still has 0.0.0.0/0 on the same port. And because neither construct has deny rules, you cannot override it from the NSG side. Remove the Security List rule first.
Mistake 2: Wiping Security List rules with a partial CLI update Security List updates are all-or-nothing. --ingress-security-rules replaces every existing ingress rule with exactly what you provide. Omit an existing rule from the update and it is permanently deleted. Always save current rules to a file, make your changes, update with the complete list.
Mistake 3: Assigning a VNIC to an NSG with no rules An NSG with no rules enforces the implicit deny — all traffic blocked. If you create an NSG, assign a VNIC to it, and then add rules, your instance loses connectivity the moment the VNIC is assigned. Add rules to the NSG before assigning VNICs.
Mistake 4: Using CIDR ranges for tier-to-tier rules A rule allowing TCP 1521 from 10.0.2.0/24 silently grants database access to every future instance added to that subnet. A rule allowing TCP 1521 from NSG:nsg-app grants access only to VNICs explicitly in nsg-app. Always use NSG-as-source for tier-to-tier rules.
Mistake 5: Forgetting egress rules when removing the Security List NSG rules are evaluated for both directions. If you migrate ingress rules to NSGs and remove the Security List egress Allow All 0.0.0.0/0 without adding NSG egress rules, your instances silently lose all outbound connectivity — including yum updates, NTP, and any OCI SDK calls.
Mistake 6: Assuming connectivity problems are always OCI networking Three layers evaluate independently: Security List, NSG, OS firewall. A blocked port at any one of these three layers produces a connection timeout. Developers spend hours debugging OCI rules when the actual block is iptables on the instance, or vice versa. Check all three before concluding any one of them is misconfigured.
Conclusion
The Security List vs NSG choice comes down to one architectural truth: you cannot restrict what you have already allowed. OCI’s OR logic means Security List and NSG rules are additive permissions, not competing controls. NSG rules cannot override Security List rules. There are no deny rules in either construct to force the issue.
The path forward is clear. Narrow your Security Lists to the absolute minimum — ICMP and genuine subnet-wide infrastructure rules only. Move everything workload-specific to NSGs. Use NSG-as-source for tier-to-tier rules so permissions follow workload identity rather than subnet location. Use nsg rules add and nsg rules update for safe incremental changes. Save and merge before every Security List update.
When you build this structure, you also build the foundation that OCI’s Zero Trust Packet Routing sits on top of — intent-based policies enforced with AND logic that no Security List permission can accidentally bypass.
The question is not whether to migrate. The question is whether your Security List currently has rules that are silently granting access you never intended — and the audit commands in this post will answer that for you in under a minute.
Key Takeaways
What to Remember 1 Security Lists + NSGs = OR logic. Either allowing is enough. NSG restrictions cannot override Security List permissions. 2 Neither Security Lists nor NSGs have deny rules. Both are allow-only with implicit deny for everything else. 3 NSG rules are additive — add, update, or remove individual rules safely. Security List rules are all-or-nothing — always save before updating. 4 East-west traffic within a subnet cannot be restricted by Security Lists — only NSGs can enforce intra-subnet isolation. 5 A fresh NSG with no rules blocks all traffic. Add rules before assigning VNICs. 6 Use NSG-as-source for tier-to-tier rules. CIDR-based rules grant access to entire subnets including future instances. 7 Three independent layers: Security List, NSG, OS firewall. All must allow for traffic to pass.
Useful References
- OCI Security Rules official comparison: docs.oracle.com/en-us/iaas/Content/Network/Concepts/securityrules.htm
- OCI Network Security Groups: docs.oracle.com/en-us/iaas/Content/Network/Concepts/networksecuritygroups.htm
- OCI Networking Best Practices (A-Team): ateam-oracle.com/oci-networking-best-practices-part-two-oci-network-security
- OCI Zero Trust Packet Routing: docs.oracle.com/en-us/iaas/Content/zero-trust-packet-routing/overview.htm
- OCI NSG CLI Reference: docs.oracle.com/en-us/iaas/tools/oci-cli/latest/oci_cli_docs/cmdref/network/nsg.html
Tags: OCI · NSG · Security Lists · VCN · Networking · Zero Trust · OR Logic · Implicit Deny · Microsegmentation · Security
메타데이터
- post_id
- 301cdc8b3ad4
- slug
- security-lists-vs-nsgs-in-oci-youre-probably-using-the-wrong-one-and-here-s-the-proof-301cdc8b3ad4
- url
- https://medium.com/@sayeedamodix/security-lists-vs-nsgs-in-oci-youre-probably-using-the-wrong-one-and-here-s-the-proof-301cdc8b3ad4
- canonical_url
- https://medium.com/@sayeedamodix/security-lists-vs-nsgs-in-oci-youre-probably-using-the-wrong-one-and-here-s-the-proof-301cdc8b3ad4
- author_url
- https://medium.com/@sayeedamodix
- status
- ok
- fetched_at
- 2026-06-12 18:14:10