The problem it solves
Instances in a private subnet have no public IP and no route to an Internet Gateway. They still need to:
- Read and write Object Storage (backups, logs, data files).
- Install OS updates from the OCI YUM mirrors.
- Call OCI service APIs (Monitoring, Logging, Vault).
A NAT Gateway would work, but traffic leaves through public endpoints and every instance needs internet egress. A Service Gateway gives private subnets a direct path to Oracle Services Network inside OCI, with no internet involved.
Two service labels: pick the right one
When you create a Service Gateway you choose one of two "services" (CIDR labels). Names include the region key:
| Label (example for Mumbai) | Covers | Use when |
|---|---|---|
OCI BOM Object Storage | Object Storage only | You only need buckets |
All BOM Services In Oracle Services Network | Object Storage plus other OCI services and the regional YUM mirrors | You want OS updates and OCI APIs privately too |
Most teams pick All Services. List what your region offers:
oci network service list --query 'data[].{name:name, id:id, cidr:"cidr-block"}' --output table
Tip
A Service Gateway only reaches services in the same region. For Object Storage in another region you still need NAT, or replication into the local region.
Step 1: Create the Service Gateway
COMPARTMENT=ocid1.compartment.oc1..aaaa...
VCN=ocid1.vcn.oc1.ap-mumbai-1.aaaa...
SVC_ID=$(oci network service list \
--query "data[?contains(name,'All')].id | [0]" --raw-output)
oci network service-gateway create \
--compartment-id $COMPARTMENT --vcn-id $VCN \
--services "[{\"serviceId\":\"$SVC_ID\"}]" \
--display-name sgw-prod
One Service Gateway per VCN is enough.
Step 2: Add a route rule in the private subnet's route table
The destination is the service CIDR label, not an IP range:
SGW=$(oci network service-gateway list --compartment-id $COMPARTMENT --vcn-id $VCN \
--query 'data[0].id' --raw-output)
SVC_CIDR=$(oci network service list --query "data[?contains(name,'All')].\"cidr-block\" | [0]" --raw-output)
oci network route-table update --rt-id <private-route-table-ocid> \
--route-rules "[{\"destination\":\"$SVC_CIDR\",\"destinationType\":\"SERVICE_CIDR_BLOCK\",\"networkEntityId\":\"$SGW\"},
{\"destination\":\"0.0.0.0/0\",\"destinationType\":\"CIDR_BLOCK\",\"networkEntityId\":\"<nat-gateway-ocid>\"}]" \
--force
Warning
route-table update --route-rulesreplaces the whole rule list. Get the current rules first (oci network route-table get --rt-id ...) and include every rule you want to keep, or you'll drop the NAT or DRG route.
With both rules in place, traffic to OCI services goes through the Service Gateway and everything else to the internet goes through NAT.
Step 3: Allow it in security rules
Egress from the private subnet must allow HTTPS to the service label. In a security list or NSG, add an egress rule:
- Destination type: Service
- Destination service:
All BOM Services In Oracle Services Network - Protocol: TCP, destination port 443
If your egress is already "all traffic to 0.0.0.0/0", nothing extra is needed.
Step 4: Test from a private instance
# Instance principal auth (needs a dynamic group + policy) or a config file
oci os ns get --auth instance_principal
oci os object list --bucket-name app-backups --auth instance_principal --limit 5
# OS updates through the regional mirror (Oracle Linux)
sudo dnf makecache
If oci os ns get hangs, check the route table and security rules. If it returns NotAuthorizedOrNotFound, networking is fine and the problem is IAM.
Step 5: Restrict a bucket to your VCN (optional)
You can make a bucket readable only from your VCN through the Service Gateway with an IAM policy condition:
Allow group AppOperators to manage objects in compartment app-prod
where all {target.bucket.name = 'app-backups', request.vcn.id = 'ocid1.vcn.oc1.ap-mumbai-1.aaaa...'}
Requests from outside the VCN (laptops, other VCNs) no longer match the Allow and are refused. Keep a separate policy for admins so you can still manage the bucket from the console.
Service Gateway vs NAT Gateway vs private endpoint
| Need | Use |
|---|---|
| Private subnet → Object Storage / OCI services in same region | Service Gateway |
| Private subnet → internet (GitHub, Docker Hub, external APIs) | NAT Gateway |
| On-prem → Object Storage privately over FastConnect | Service Gateway with a DRG transit route (transit routing) |
| Private IP address for a specific service resource (for example Autonomous DB) | Private endpoint |
Prevent problems later
- Add a Service Gateway to every VCN in your landing zone template, routed from all private subnets.
- Move bulk backup and log traffic off NAT; it's simpler and avoids NAT throughput limits.
- Keep route table changes in Terraform so a manual
--route-rulesupdate can't silently delete routes. - Use
request.vcn.idconditions for sensitive buckets.