Home / OCI

OCIHow-toAdvanced

OCI Load Balancer rule sets: HTTP-to-HTTPS redirects, security headers and IP allow lists with the CLI

Build OCI Load Balancer rule sets with the CLI to redirect HTTP to HTTPS, add HSTS and strip headers, restrict methods and allow-list IPs, without wiping existing rules or listener settings.

Deepak Bhardwaj7 min read

Applies toOCI Flexible Load Balancer (HTTP/HTTPS listeners), OCI CLI 3.x, Terraform oci_load_balancer_rule_set

Goal

You want the OCI load balancer to handle plumbing that usually ends up in every backend: redirect port 80 to HTTPS, add Strict-Transport-Security, hide the backend Server header, and lock an internal listener down to known CIDRs. Rule sets do all of this on HTTP and HTTPS listeners. They don't apply to TCP listeners or to the Network Load Balancer.

Two behaviours catch people out:

  1. oci lb rule-set update replaces the whole item list. Any rule you leave out is deleted.
  2. An ALLOW rule turns the listener into deny by default. Traffic that doesn't match is rejected.

1. Inventory what's there

LB=ocid1.loadbalancer.oc1..xxxx

oci lb rule-set list --load-balancer-id $LB --output json

oci lb load-balancer get --load-balancer-id $LB \
  --query 'data.listeners.* | [].{name:name,port:port,proto:protocol,backend:"default-backend-set-name",rules:"rule-set-names"}' \
  --output table

Save the full listener definitions before you change anything. You'll need them to roll back:

oci lb load-balancer get --load-balancer-id $LB --query 'data.listeners' > listeners-before.json

Action reference

Action Use it for Key fields
REDIRECT HTTP→HTTPS, old path→new path conditions (PATH), redirectUri, responseCode (301, 302, 303, 307, 308)
ADD_HTTP_RESPONSE_HEADER / ADD_HTTP_REQUEST_HEADER HSTS, tracing or identity headers header, value (replaces an existing header of the same name)
REMOVE_HTTP_RESPONSE_HEADER / REMOVE_HTTP_REQUEST_HEADER Hide Server, X-Powered-By header
EXTEND_HTTP_*_HEADER_VALUE Prefix or suffix an existing value header, prefix, suffix
ALLOW IP or VCN allow list conditions (SOURCE_IP_ADDRESS, SOURCE_VCN_ID, SOURCE_VCN_IP_ADDRESS), description
CONTROL_ACCESS_USING_HTTP_METHODS Allow only GET/POST/... allowedMethods, statusCode (default 405)
HTTP_HEADER Large headers or invalid characters httpLargeHeaderSizeInKb (8/16/32/64), areInvalidCharactersAllowed
IP_BASED_MAX_CONNECTIONS Per-client connection caps defaultMaxConnections, ipMaxConnections

Limits: up to 20 rules in one rule set and 50 rules per load balancer. Rule sets belong to a single load balancer and can't be shared. Check the current service limits before you design around them.

The CLI accepts camelCase JSON. To get a skeleton for your CLI version, run oci lb rule-set create --generate-param-json-input items.

2. HTTP-to-HTTPS redirect

redirect-https.json:

[
  {
    "action": "REDIRECT",
    "conditions": [
      { "attributeName": "PATH", "attributeValue": "/", "operator": "PREFIX_MATCH" }
    ],
    "redirectUri": {
      "protocol": "HTTPS",
      "host": "{host}",
      "port": 443,
      "path": "{path}",
      "query": "{query}"
    },
    "responseCode": 301
  }
]
oci lb rule-set create --load-balancer-id $LB --name redirect_https \
  --items file://redirect-https.json --wait-for-state SUCCEEDED

The {host}, {path} and {query} tokens are case sensitive and keep the incoming values. If you don't change at least one component (here the protocol and port), the client loops on redirects. A listener can have only one redirect rule per incoming path, and the incoming path condition can't include a query string.

Tip

Start with 302 while you test, and switch to 301 or 308 once it works. Browsers cache permanent redirects aggressively, so a mistake with 301 sticks on clients after you fix it. Use 307 or 308 if clients send POSTs to the HTTP endpoint and must keep the method.

3. Security headers on the HTTPS listener

https-headers.json:

[
  {
    "action": "ADD_HTTP_RESPONSE_HEADER",
    "header": "Strict-Transport-Security",
    "value": "max-age=31536000; includeSubDomains"
  },
  {
    "action": "ADD_HTTP_RESPONSE_HEADER",
    "header": "X-Content-Type-Options",
    "value": "nosniff"
  },
  { "action": "REMOVE_HTTP_RESPONSE_HEADER", "header": "Server" },
  { "action": "REMOVE_HTTP_RESPONSE_HEADER", "header": "X-Powered-By" },
  {
    "action": "CONTROL_ACCESS_USING_HTTP_METHODS",
    "allowedMethods": ["GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS"],
    "statusCode": 405
  }
]
oci lb rule-set create --load-balancer-id $LB --name https_headers \
  --items file://https-headers.json --wait-for-state SUCCEEDED

Header values can't contain $ or {variable} placeholders. Only one method-control list is allowed per listener. Rejected methods never reach the backend.

Warning

Once browsers have cached HSTS, they refuse plain HTTP to that host for the whole max-age. Only add includeSubDomains if every subdomain serves valid HTTPS. Start with a short max-age (for example 300) and raise it after a day.

4. IP allow list for an internal or admin listener

allow-corp.json:

[
  {
    "action": "ALLOW",
    "description": "Corporate egress and VPN pool",
    "conditions": [ { "attributeName": "SOURCE_IP_ADDRESS", "attributeValue": "203.0.113.0/24" } ]
  },
  {
    "action": "ALLOW",
    "description": "On-prem via FastConnect",
    "conditions": [ { "attributeName": "SOURCE_IP_ADDRESS", "attributeValue": "10.20.0.0/16" } ]
  }
]
oci lb rule-set create --load-balancer-id $LB --name allow_corp \
  --items file://allow-corp.json --wait-for-state SUCCEEDED

Caution

When this rule set is attached, the listener rejects every source that isn't listed. Before you attach it, include health check and monitoring sources, partner IPs, and any NAT addresses your users come through. Attach it to one listener first and test from outside the list.

ALLOW rules complement NSG and security list rules; they don't replace them. Keep the network rules in place, and use ALLOW where you need listener-level control, for example a shared load balancer with one public listener and one restricted listener.

5. Attach rule sets to listeners

listener update requires the listener's core settings, and --rule-set-names is the complete list for that listener. Copy every value from listeners-before.json:

# Port 80 listener: redirect only
oci lb listener update --load-balancer-id $LB --listener-name http-80 \
  --default-backend-set-name bs-app --port 80 --protocol HTTP \
  --rule-set-names '["redirect_https"]' \
  --force --wait-for-state SUCCEEDED

Before you run the update on an HTTPS listener, pass its existing SSL settings as well: certificate name or IDs, verify-peer, cipher suite. Pass the hostnames, path route set, routing policy and idle timeout too. Any optional value you leave out may be reset. If you're not sure you've captured them all, attach rule sets in the console (Listeners → Edit → Rule sets). That changes only the rule-set selection.

6. Test

curl -sI http://app.example.com/login?x=1 | grep -iE '^(HTTP|location)'
# expect 301 and Location: https://app.example.com:443/login?x=1

curl -sI https://app.example.com/ | grep -iE 'strict-transport|server|x-powered'
curl -s -o /dev/null -w '%{http_code}\n' -X TRACE https://app.example.com/   # expect 405

To test the allow list, run curl from a source outside the list (Cloud Shell has its own public egress IP). You should see a rejection, while a listed source gets a normal response.

7. Change a rule set safely

update overwrites the item list. Fetch the current items, edit them, and send the whole list back:

oci lb rule-set get --load-balancer-id $LB --rule-set-name https_headers \
  --query 'data.items' > items.json
# edit items.json: add or remove entries, keep the rest
oci lb rule-set update --load-balancer-id $LB --rule-set-name https_headers \
  --items file://items.json --force --wait-for-state SUCCEEDED

The get output uses kebab-case keys. If update rejects the file, convert the keys to camelCase or regenerate the item list from your source-controlled JSON. Source control is the better habit in any case.

To roll back quickly, detach the rule set from the listener (or reattach the previous list). Delete it only after it's detached:

oci lb rule-set delete --load-balancer-id $LB --rule-set-name allow_corp --force

Troubleshooting

Symptom Cause Fix
Browser shows "too many redirects" Redirect attached to the HTTPS listener too, or nothing changed in redirectUri Attach redirect only to the port 80 listener, and set protocol and port
Everyone gets rejected after a change ALLOW rule set attached without all source CIDRs Detach it, add the missing CIDRs, reattach
A header rule vanished rule-set update sent a partial list Restore from source control and send the full list
Other listener settings changed after attach listener update reset optional fields Reapply from listeners-before.json, or use the console
Backend still sees the header you removed REMOVE_HTTP_RESPONSE_HEADER used for a request header Use REMOVE_HTTP_REQUEST_HEADER
400 errors with large cookies Default header buffer too small Add HTTP_HEADER with a larger httpLargeHeaderSizeInKb

Prevent it next time

  • Keep rule sets in Terraform (oci_load_balancer_rule_set) or in JSON in source control. Never hand-edit the only copy.
  • Before every listener update, save the listener JSON to the change ticket.
  • Name rule sets by purpose (redirect_https, https_headers, allow_corp) so the listener view explains itself.
  • Run the curl checks in a synthetic monitor so a missing redirect or HSTS header raises an alarm.