Skip to main content

OCI Compute Instances Should Not Have Public IP Addresses

More Info:

Compute instances should not have public IP addresses assigned. Public IPs expose instances directly to internet-based attacks and should be avoided in favor of load balancers or bastion hosts.

Risk Level

Critical

Address

Compliance, Security

Compliance Standards

  • APRA CPS 234 (Australia)
  • AWS Startup Security Baseline
  • AWS Well Architected Framework
  • BSI C5 (Germany)
  • Brazil LGPD
  • CCPA / CPRA (California)
  • CIS Critical Security Controls v8
  • CMMC 2.0
  • CSA Cloud Controls Matrix v4
  • DPDPA
  • Digital Operational Resilience Act (EU)
  • FedRAMP
  • HIPAA
  • HITRUST CSF
  • ISO/IEC 27017
  • ISO/IEC 27018
  • ISO/IEC 27701
  • KSA PDPL
  • MAS Technology Risk Management (Singapore)
  • MITRE ATT&CK (Cloud)
  • NIS2 Directive
  • NIST CSF
  • NIST SP 800-171
  • NYDFS 23 NYCRR 500
  • PCI
  • Reserve Bank of India (RBI) Cyber Security Framework
  • SOC2
  • SWIFT Customer Security Controls Framework
  • Sarbanes-Oxley IT General Controls
  • Securities and Exchange Board of India (SEBI) - Cloud Security Adoption Framework
  • UK NCSC Cyber Assessment Framework

Triage and Remediation

Remediation

Using Console

Below are step‑by‑step console instructions to remediate “OCI Compute Instances Should Not Have Public IP Addresses.”

1. Identify Instances With Public IPs

  1. Sign in to OCI Console.
  2. Choose the correct Region (top right).
  3. Open the navigation menu → ComputeInstances.
  4. Select the Compartment where your instances reside.
  5. For each instance:
    • Click the instance name.
    • Under Resources (left side), click Attached VNICs.
    • Click the primary VNIC.
    • In the VNIC details, check:
      • Public IP field
      • If present, note the Public IP address and whether it’s ephemeral or reserved.

You will remediate each instance/VNIC that has a Public IP.


2. Remove Public IP From the Instance (VNIC)

You cannot detach a primary VNIC, but you can remove or change its Public IP.
For safety, stop the instance if it’s production and you want to avoid connection issues during change.

  1. On the instance details page, click Stop.
  2. Wait until the state is Stopped.

2.2 Remove or Change the Public IP

  1. On the same instance page, under Resources, click Attached VNICs.
  2. Click the primary VNIC (usually named something like instance-name (Primary VNIC)).
  3. In the VNIC details page:
    • Locate the IPv4 Addresses section.
    • Find the Primary private IP row.
    • In the Public IP column:
      • Click the Actions menu (three dots) or the link for the public IP.

Depending on the type:

If it’s an Ephemeral Public IP:

  1. In the Public IP details, click Edit or ActionsUnassign / Remove.
  2. Confirm the unassignment.
    • After this, the Primary private IP will show No public IP address.

If it’s a Reserved Public IP:

  1. In the Public IP details, click Edit or ActionsUnassign.
  2. Confirm.
  3. (Optional cleanup) If you no longer need that reserved IP:
    • Go to navigation menu → NetworkingPublic IPs.
    • Choose the correct Compartment.
    • Find that Reserved Public IP, click its name.
    • Click Release Public IP to return it to the pool.

2.3 Start the Instance (if you stopped it)

  1. Return to the instance page.
  2. Click Start.
  3. Confirm that applications are reachable via private paths (VPN, FastConnect, Bastion, etc.).

3. Prevent Future Public IP Assignment at Subnet Level

To avoid new instances in that subnet getting public IPs:

  1. Open navigation menu → NetworkingVirtual Cloud Networks.
  2. Select the VCN where the instance’s subnet resides.
  3. Under Resources, click Subnets.
  4. Click the subnet that was used by your instance.
  5. Click Edit.
  6. Set:
    • Public IP address:
      • Uncheck or choose option that disables public IP assignment (wording may be “Do not assign public IPv4 addresses” or similar).
  7. Click Save changes.

New instances or VNICs in this subnet will not be allowed to have public IPs.


4. (Optional) Use OCI Bastion for Private-Only Access

If you used the public IP for SSH/RDP:

  1. Open navigation menu → Identity & SecurityBastion.
  2. Create a Bastion in:
    • The same Region.
    • A public subnet with proper security list/NSG rules.
  3. Configure a Session (SSH, RDP, etc.) from your workstation through the Bastion to the private IP of your instance.
  4. Confirm you can connect without needing a public IP on the instance.

5. Confirm Remediation for Monitoring/Compliance

To ensure OCI monitoring/compliance tools recognize the remediation:

  1. Wait for the next evaluation cycle (usually a few minutes up to an hour, depending on the security/monitoring service).
  2. In your OCI Security/Monitoring or Cloud Guard dashboards, confirm that:
    • The problem “Compute Instances Should Not Have Public IP Addresses” for those instances is either:
      • Marked Resolved, or
      • No longer shown as active.

You have now removed public IP addresses from your OCI Compute instances and hardened the subnet to prevent new public IPs from being assigned.

Using CLI

Below is a focused, step‑by‑step way to remediate “OCI Compute Instances Should Not Have Public IP Addresses” using the OCI CLI. The core actions are:

  1. Identify instances with public IPs
  2. Remove public IPs from their VNICs
  3. Prevent new public IP assignment at subnet level

1. Prerequisites

  • OCI CLI installed and configured (oci setup config)
  • You know:
    • Your compartment-ocid
    • The region you’re working in (--region if different from default)

2. Identify Compute Instances with Public IPs

2.1. List instances in a compartment

oci compute instance list \
--compartment-id <compartment-ocid> \
--all \
--query "data[?\"lifecycle-state\"=='RUNNING' || \"lifecycle-state\"=='STOPPED'].{id:id,\"display-name\":\"display-name\"}" \
--output table

For each instance, get its VNICs and public IPs.

2.2. For each instance, get its VNIC attachments

INSTANCE_OCID=<instance-ocid>

oci compute vnic-attachment list \
--compartment-id <compartment-ocid> \
--instance-id $INSTANCE_OCID \
--all \
--query "data[].{\"id\":id,\"vnic-id\":\"vnic-id\"}" \
--output table

2.3. For each VNIC, check if it has a public IP

VNIC_ID=<vnic-ocid>

oci network vnic get \
--vnic-id $VNIC_ID \
--query "data.{\"vnic-id\":id,\"public-ip\":\"public-ip\",\"is-primary\":\"is-primary\"}" \
--output table

If public-ip is not null, that VNIC currently has a public IP.


3. Remove Public IP Addresses from VNICs

There are two types of public IPs: ephemeral and reserved. You unassign them differently.

3.1. Determine the public IP object (ephemeral vs reserved)

Use the VNIC’s public-ip from the previous step (IP address string).

IP_ADDRESS=<public-ip-address>

oci network public-ip list \
--compartment-id <compartment-ocid> \
--all \
--query "data[?\"ip-address\"=='$IP_ADDRESS']"

If it returns a resource with an OCID and "lifetime": "RESERVED", it’s a reserved public IP.
If it doesn’t return anything, it’s likely an ephemeral public IP associated directly to the VNIC.


3.2. If the public IP is ephemeral (most common)

Use vnic update to remove the public IP:

oci network vnic update \
--vnic-id $VNIC_ID \
--assign-public-ip false

This detaches the ephemeral public IP from the VNIC.


3.3. If the public IP is reserved

First, unassign the reserved public IP from the VNIC, then optionally delete it.

3.3.1. Find the reserved public IP OCID

oci network public-ip list \
--compartment-id <compartment-ocid> \
--all \
--query "data[?\"ip-address\"=='$IP_ADDRESS'].id | [0]" \
--raw-output

Save it:

PUBLIC_IP_ID=<public-ip-ocid>

3.3.2. Unassign the reserved public IP from the VNIC

oci network public-ip update \
--public-ip-id $PUBLIC_IP_ID \
--private-ip-id "" \
--vnic-id ""

Some older API patterns require you to update the private-ip-id to null (or assign to a different private IP). If the above fails, you can:

  1. Get the private IP behind the VNIC:
    oci network private-ip list \
    --vnic-id $VNIC_ID \
    --query "data[?\"is-primary\"==\`true\`].id | [0]" \
    --raw-output
  2. Use oci network public-ip update to dissociate or reassign as required (depends on tenancy policy and version). If removing directly is not allowed, you may need to reassign to some “holding” private IP that is not used by a compute instance.

3.3.3. Optionally delete the reserved public IP (to avoid reuse)

oci network public-ip delete \
--public-ip-id $PUBLIC_IP_ID \
--force

4. Prevent Future Public IP Assignment at Subnet Level

To ensure instances in a subnet cannot get public IPs:

4.1. Get subnet details and current setting

SUBNET_ID=<subnet-ocid>

oci network subnet get \
--subnet-id $SUBNET_ID \
--query "data.{id:id,\"display-name\":\"display-name\",\"prohibit-public-ip-on-vnic\":\"prohibit-public-ip-on-vnic\"}" \
--output table

If prohibit-public-ip-on-vnic is false, change it to true.

4.2. Update the subnet

oci network subnet update \
--subnet-id $SUBNET_ID \
--prohibit-public-ip-on-vnic true \
--force

From now on, new VNICs in this subnet cannot have public IPs. Existing VNICs with public IPs are not automatically stripped; you must remediate them as in steps 2–3.


5. Bulk / Scripted Remediation Pattern (Optional)

To remediate all instances in a compartment:

  1. List all VNICs that currently have a public-ip:

    oci compute instance list --compartment-id <compartment-ocid> --all \
    | jq -r '.data[].id' \
    | while read INSTANCE_ID; do
    oci compute vnic-attachment list --compartment-id <compartment-ocid> --instance-id "$INSTANCE_ID" --all \
    | jq -r '.data[]."vnic-id"' \
    | while read VNIC_ID; do
    oci network vnic get --vnic-id "$VNIC_ID" \
    | jq -r '.data | select(.["public-ip"] != null) | ."public-ip"' \
    | while read IP; do
    echo "Removing public IP $IP from VNIC $VNIC_ID (instance $INSTANCE_ID)"
    oci network vnic update --vnic-id "$VNIC_ID" --assign-public-ip false
    done
    done
    done
  2. Then update subnets as in section 4 to prevent re‑introduction.


If you share your compartment OCID / subnet structure (sanitized), I can help format exact ready‑to‑run commands for your layout.

Using Python

Below is a concise, Python‑based approach to detect and remediate OCI Compute instances that have public IPs by unassigning those IPs from their VNICs.

Assumptions:

  • You have oci Python SDK installed (pip install oci).
  • Your ~/.oci/config is set up, or you otherwise pass config programmatically.
  • You understand that unassigning public IPs may break connectivity (SSH/RDP) to those instances.

1. Install and Import OCI SDK

pip install oci
import oci
from oci.core import ComputeClient, VirtualNetworkClient

2. Create OCI Clients

config = oci.config.from_file("~/.oci/config", "DEFAULT")
compute_client = ComputeClient(config)
vcn_client = VirtualNetworkClient(config)

compartment_id = "<your_compartment_ocid>"

3. List Instances and Their VNICs

# Get all instances in the compartment
instances = oci.pagination.list_call_get_all_results(
compute_client.list_instances,
compartment_id=compartment_id,
lifecycle_state="RUNNING" # or omit to include all
).data

Now get each instance’s VNIC attachments:

vnic_attachments = []
for instance in instances:
vas = oci.pagination.list_call_get_all_results(
compute_client.list_vnic_attachments,
compartment_id=compartment_id,
instance_id=instance.id
).data
vnic_attachments.extend(vas)

4. Detect VNICs with Public IPs

vnics_with_public_ip = []

for va in vnic_attachments:
vnic = vcn_client.get_vnic(va.vnic_id).data
if vnic.public_ip:
vnics_with_public_ip.append((vnic, va))

At this point you have all VNICs with public IPs.


5. Unassign the Public IPs

There are two cases in OCI:

  1. Ephemeral Public IPs (assigned directly to VNIC).
  2. Reserved Public IPs (separate OCI resource, associated with a private IP).

5.1. Unassign Ephemeral Public IP

for vnic, va in vnics_with_public_ip:
# For ephemeral public IPs, the public IP is directly tied to the VNIC primary private IP.
# Get the private IP resource:
private_ips = oci.pagination.list_call_get_all_results(
vcn_client.list_private_ips,
vnic_id=vnic.id
).data

for priv_ip in private_ips:
# if this private IP has an associated public IP (ephemeral)
if priv_ip.public_ip:
print(f"Unassigning ephemeral public IP from VNIC {vnic.id} (Instance: {va.instance_id})")
# Update private IP to remove public IP
update_details = oci.core.models.UpdatePrivateIpDetails(
defined_tags=priv_ip.defined_tags,
display_name=priv_ip.display_name,
freeform_tags=priv_ip.freeform_tags,
hostname_label=priv_ip.hostname_label
# No explicit field to “unset” public_ip; we remove via UpdatePublicIp or delete reserved IP
)
# In practice, ephemeral IPs are deleted by deleting/ resetting the association:
# For ephemeral public IPs, you usually call:
# vcn_client.update_public_ip(...) only for reserved; for ephemeral, you use DeletePublicIp when exposed.
# OCI exposes ephemeral public IPs via get_public_ip_by_ip_address for deletion.
public_ip_obj = vcn_client.get_public_ip_by_ip_address(
get_public_ip_by_ip_address_details=oci.core.models.GetPublicIpByIpAddressDetails(
ip_address=priv_ip.public_ip
)
).data

# Delete (unassign) the public IP
vcn_client.delete_public_ip(public_ip_obj.id)

Note: For ephemeral public IPs, they are modeled as a PublicIp resource of type EPHEMERAL. Deleting that PublicIp unassigns it from the private IP.

5.2. Unassign Reserved Public IPs

Reserved public IPs may be attached to specific private IPs. Detection is similar; identify the PublicIp resource and delete or “unassign” it.

# List all reserved public IPs in compartment, then detach them
public_ips = oci.pagination.list_call_get_all_results(
vcn_client.list_public_ips,
compartment_id=compartment_id,
scope="REGION"
).data

for pub_ip in public_ips:
if pub_ip.lifecycle_state != "ASSIGNED":
continue

# If you want to remove all instance public IPs:
if pub_ip.assigned_entity_id: # usually the private IP OCID
print(f"Unassigning reserved public IP {pub_ip.ip_address} ({pub_ip.id})")
# Delete reserved public IP – this unassigns it
vcn_client.delete_public_ip(pub_ip.id)

6. Wrap into a “Remediation Script”

Combine the logic into a script that:

  1. Lists instances and their VNICs.
  2. Finds all VNICS/private IPs with public IPs.
  3. Resolves the corresponding PublicIp resources.
  4. Deletes (unassigns) those PublicIp resources.

Skeleton:

import oci

def remove_public_ips_from_instances(compartment_id, config_profile="DEFAULT"):
config = oci.config.from_file("~/.oci/config", config_profile)
compute_client = oci.core.ComputeClient(config)
vcn_client = oci.core.VirtualNetworkClient(config)

instances = oci.pagination.list_call_get_all_results(
compute_client.list_instances,
compartment_id=compartment_id,
lifecycle_state="RUNNING"
).data

for instance in instances:
vas = oci.pagination.list_call_get_all_results(
compute_client.list_vnic_attachments,
compartment_id=compartment_id,
instance_id=instance.id
).data

for va in vas:
vnic = vcn_client.get_vnic(va.vnic_id).data
if not vnic.public_ip:
continue

private_ips = oci.pagination.list_call_get_all_results(
vcn_client.list_private_ips,
vnic_id=vnic.id
).data

for priv_ip in private_ips:
if not priv_ip.public_ip:
continue

# Get the public IP resource
public_ip_obj = vcn_client.get_public_ip_by_ip_address(
get_public_ip_by_ip_address_details=oci.core.models.GetPublicIpByIpAddressDetails(
ip_address=priv_ip.public_ip
)
).data

print(f"Unassigning public IP {public_ip_obj.ip_address} from instance {instance.display_name} ({instance.id})")
vcn_client.delete_public_ip(public_ip_obj.id)

if __name__ == "__main__":
remove_public_ips_from_instances("<your_compartment_ocid>")

7. (Optional) Monitoring / Alerting

To integrate this with “compute monitoring”:

  1. Use OCI Monitoring / Alarms or Cloud Guard to detect instances with public IPs (e.g., via IAM/Config rules or Cloud Guard detector).
  2. Configure an Alarm or Cloud Guard Responder that triggers a Notification (OCI Events + Functions or OCI Notifications to a function).
  3. That Function can run the Python logic above (packaged into a function) to automatically remediate.

If you specify whether you want a standalone script, OCI Function code, or integration with Cloud Guard/Events, I can give a tailored Python example for that environment.

Using Terraform
resource "oci_core_instance" "COMPUTE_INSTANCE_NAME" {
# Replace with your compartment and availability details
compartment_id = "OCID_OF_COMPARTMENT"
availability_domain = "AVAILABILITY_DOMAIN_NAME"
shape = "VM.SHAPE_NAME"
display_name = "COMPUTE_INSTANCE_NAME"

source_details {
source_type = "image"
source_id = "OCID_OF_IMAGE"
}

create_vnic_details {
subnet_id = "OCID_OF_SUBNET"
hostname_label = "HOSTNAME_LABEL"
assign_public_ip = "false" # Ensure no public IP is assigned
}

# ... any other required arguments ...
}

Setting assign_public_ip = "false" (or omitting it when the subnet is private-by-default) prevents the instance from receiving a public IP; changing this on an existing instance forces replacement (destroy and recreate) of the instance/VNIC in Terraform.

After the change, terraform plan should show the assign_public_ip argument changing from "true" (or "yes") to "false" with a planned -/+ replacement for the oci_core_instance resource.