The goal
A partner allowlists your server's public IP, or a DNS record points straight at it. You need to rebuild the instance, or move the service to a new one, and the address must not change.
By default, an instance in a public subnet gets an ephemeral public IP. It is tied to that instance's private IP and is deleted when the instance is terminated. A reserved public IP belongs to your compartment. You can unassign it from one private IP and assign it to another, and it stays yours until you delete it.
| Ephemeral | Reserved | |
|---|---|---|
| Created | Automatically at launch (or added later) | By you, ahead of time |
| Survives instance termination | No | Yes |
| Can move to another instance | No | Yes, within the same region |
| Scope | Availability domain | Region |
| Convert one to the other | Not possible | Not possible |
Warning
You can't convert an ephemeral IP into a reserved one. If you terminate an instance that only has an ephemeral IP, that address is gone for good. Plan the move to a reserved IP before the address becomes something people depend on.
Before you start
- The target VNIC must be in a public subnet, one that allows public IPs on VNICs. You can check with
oci network subnet get --subnet-id <ocid> --query 'data."prohibit-public-ip-on-vnic"', which must returnfalse. - The subnet's route table needs a route to an internet gateway.
- A private IP can have only one public IP. Remove the ephemeral one first.
- Reserved public IPs count against a service limit. Check the current limits for your tenancy before reserving many.
Step 1: Create the reserved public IP
C=ocid1.compartment.oc1..aaaa...
oci network public-ip create -c "$C" --lifetime RESERVED \
--display-name app-prod-pip \
--query 'data.{id:id,ip:"ip-address",state:"lifecycle-state"}'
Note the ip-address. You can hand this to partners and put it in DNS now, before any cutover.
List what you already have:
oci network public-ip list -c "$C" --scope REGION --lifetime RESERVED --all \
--query 'data[].{name:"display-name",ip:"ip-address",state:"lifecycle-state",assignedTo:"assigned-entity-id"}' \
--output table
--scope REGION is required for reserved IPs. An assignedTo value that is empty means the IP is unassigned and ready to use.
Step 2: Find the instance's primary private IP
A public IP is assigned to a private IP object, not to the instance. Get the private IP OCID of the instance's primary VNIC:
INSTANCE=ocid1.instance.oc1..aaaa...
VNIC=$(oci compute instance list-vnics --instance-id "$INSTANCE" \
--query 'data[?"is-primary"]|[0].id' --raw-output)
PRIV=$(oci network private-ip list --vnic-id "$VNIC" \
--query 'data[?"is-primary"]|[0].id' --raw-output)
echo "$VNIC $PRIV"
Step 3: Remove the ephemeral IP (if any)
oci network public-ip get --private-ip-id "$PRIV" \
--query 'data.{id:id,ip:"ip-address",lifetime:lifetime}'
If this returns an EPHEMERAL IP, delete it. Deleting it is the only way to remove an ephemeral IP:
oci network public-ip delete --public-ip-id <ephemeral-public-ip-ocid> --force
Caution
The instance loses internet reachability on that address immediately. Do this in the change window. If the old address is still in use, run Step 4 straight away.
If the command returns "not found", the private IP has no public IP and you can go on.
Step 4: Assign the reserved IP
PIP=ocid1.publicip.oc1..aaaa...
oci network public-ip update --public-ip-id "$PIP" --private-ip-id "$PRIV" \
--wait-for-state ASSIGNED
Verify from the instance and from outside:
# On the instance
curl -s http://169.254.169.254/opc/v2/vnics/ -H "Authorization: Bearer Oracle" | head
# From your workstation
ssh opc@<reserved-ip-address>
The instance metadata service shows private addressing only. The public IP is NAT'd by the internet gateway and never appears on the guest OS interface. That is expected, so don't add it to the OS network config.
Step 5: Move the IP to a replacement instance
When the new instance is built and tested on its own (temporary) ephemeral IP or a private address:
- Find the new instance's primary private IP (Step 2) and remove any ephemeral IP (Step 3).
- Unassign the reserved IP from the old instance, or skip straight to reassigning it:
# Unassign: returns the IP to your reserved pool
oci network public-ip update --public-ip-id "$PIP" --private-ip-id ""
# Assign to the new instance's private IP
oci network public-ip update --public-ip-id "$PIP" --private-ip-id "$NEW_PRIV" \
--wait-for-state ASSIGNED
If you call update with the new private IP directly, OCI moves it in one call. The unassign from the old private IP always succeeds. The assignment to the new one is asynchronous and can fail without telling you, for example because the new private IP already has a public IP. Always confirm the result:
oci network public-ip get --public-ip-id "$PIP" \
--query 'data.{state:"lifecycle-state",assignedTo:"assigned-entity-id"}'
assignedTo should be the new private IP OCID and the state ASSIGNED.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Update fails, private IP already has a public IP | Ephemeral IP still attached | Delete the ephemeral IP (Step 3) |
| Error about public IPs not allowed | Subnet is private (prohibit-public-ip-on-vnic: true) | Use a VNIC in a public subnet, or put a load balancer in front |
IP shows ASSIGNED but nothing connects | Missing internet gateway route, security list/NSG rule or OS firewall | Check the route table, ingress rules and firewalld |
LimitExceeded on create | Reserved public IP limit reached | Delete unused reserved IPs or request a limit increase |
| Can't assign an IP from another region | Reserved IPs are regional | Create a new reserved IP in the target region and update DNS |
Find unused reserved IPs that still cost a limit slot:
oci network public-ip list -c "$C" --scope REGION --lifetime RESERVED --all \
--query 'data[?"lifecycle-state"==`AVAILABLE`].{name:"display-name",ip:"ip-address",id:id}' \
--output table
Prevent it next time
- For anything partners allowlist or DNS points at, assign a reserved IP at build time. In Terraform, use an
oci_core_public_ipresource withlifetime = "RESERVED"rather than relying onassign_public_ipat launch. - Better still, put stable public entry points on a load balancer or network load balancer, which can also use a reserved IP. Instances behind it can then be replaced freely.
- Tag reserved IPs with owner and purpose, and review
AVAILABLEones monthly. - Record the reserved IP OCID in your runbook so the cutover is two commands, not a search.