Skip to main content

The firewall and exposure

Two firewalls decide who reaches what: the server's own, for the console and the management API, and one per instance, for its site. Both take rules by address, range, country or continent. Below them, a host firewall decides which ports answer the network at all.

The console and the API​

Security > Firewall decides who may reach the console and the API: rules by address, CIDR range, country or continent, asserted per surface and per location rather than per server.

The Rules tab has a sub-tab per door, each named with its port as this server is configured and saying in one line what it covers:

Sub-tabWhat its rules govern
LogNothing: what the rules on every port admitted and refused, with the port and the path of each request
Console (5322)The console on its own port. These rules never apply to port 443
API (5312)The management API on its own port. These rules never apply to port 443
Published (443)The consoles served on port 443: see Publishing consoles on port 443

The Configuration tab holds the settings that are not rules: the management API's failure budgets in its Rate limiting card, described on Rate limits. An old link to the Rate limits tab opens Configuration.

On a new server, and after Reset, each surface starts with its Allow everyone rule logging the callers it admits, and Default Deny & Log answering a refused caller with Close the connection: nothing comes back, not even a page saying something is there. A surface you have saved keeps the answers you chose. An instance's own firewall keeps its defaults.

What the server's own parts send to the management API on the server itself - the console, the host agent and ohm run on the host - is not recorded in the log: it is the server talking to itself, and the console alone calls the API several times a minute for every open page. The server itself, an SSH tunnel to the console included, is always admitted, by a last rule of each surface that is not logged.

Machines on the server's private networks are judged by the rules like anyone else. A machine on your local network reaches the console or the API only when a Console or API rule admits it. The API's shipped rules admit 10.0.0.0/8 and 192.168.0.0/16, as ordinary rules you can narrow or remove; the console's lockout warning spares only the server itself.

The server's own container networks are not your local network. An Odoo instance, a code editor or anything else running in a container on the server cannot reach the console or the management API directly: the connection is closed before any rule is consulted, and the log records it as "a container on this server; the console and the API are not reachable from the server's own container networks". Nothing the product runs needs that path, and the console's own address on port 443 keeps working. A tool you run in a container on the same server and point at the management API is refused the same way; run it with host networking, or from another machine.

The log below the rules records what was refused and where it came from, coloured by whether anything is still arriving from there.
A location no rule covers refuses

Rules are asserted per location, so "no rule covers this" is a refusal and not a shrug. Leave the surface open until your own address is in the allow-list, and check the refusal log before you narrow it. Locking yourself out of the console is recoverable only from the host.

A license without access rules resets them

When the license stops carrying access rules, the server's firewall goes back to its baseline - the Web console open, the management API closed to everything but the server itself and the 10.0.0.0/8 and 192.168.0.0/16 networks - and every instance's rules go back to the default preset. The rules you wrote are kept and put back by themselves when a license carries access rules again. Meanwhile a remote address you admitted to the management API, a console that manages this server as an environment included, is not admitted. See When the license changes.

Publishing consoles on port 443​

The consoles answer on their own ports. Security > Firewall > Rules > Published (443) can also serve them on port 443, at the server's host name, each under its own path:

ConsolePathOffered while
Management console/adminAlways
pgAdmin/pgadminAlways
Portainer/portainerPortainer is installed
Metabase/metabaseAnalytics is switched on

Each rule there governs one console, chosen under Governs from the consoles this server has installed, and admits whoever it names, as an instance's rules do. An enabled rule is what publishes its console: a console no enabled rule governs is not served on port 443 at all, and whatever no rule admits falls to Default Deny & Log, which refuses it and logs it. With several rules for one console, any of them admits. A new server, and Reset, hold only Default Deny & Log, so nothing is published until you add a rule.

While the Management console is published, https://<the server's host name>/ sends the browser to /admin. The web server that serves your instances answers for the host name with the console's own certificate, the one port 5322 carries, and picks up every renewal of it, so a Let's Encrypt certificate for the host name covers both. While Metabase is published, the analytics service's Open button opens it under /metabase.

The doors are kept apart: the Console (5322) and API (5312) rules never decide who reaches port 443, and the Published (443) rules never decide who reaches 5322 or 5312. Every request on port 443 is judged by the management API; one it cannot judge gets Default Deny & Log's answer. Rows on the Log sub-tab carry the path, such as Published (443) /admin.

Before publishing a console:

  • The host name must point at this server, and no instance may use the same name.
  • Port 443 is open to everyone (the firewall script opens it for the instances), so the rules here are what limits who reaches the console's sign-in page.
  • The consoles' own ports stay as they are. Close them in your firewall if you only want port 443.

Adding or changing the Published (443) rules needs a license that carries access rules.

A server that had the console on port 443

Earlier releases had a Console on port 443 switch on Configuration > General. It is gone, and the update carries no rule over: a server that had it on no longer serves the console on port 443 after the update, and Administrators see The console is no longer served on port 443 on every page. Add a rule that governs Management console /admin on Published (443) to publish it again, now at https://<the server's host name>/admin, or press Dismiss. The notice goes once a rule publishes the console.

An instance's own firewall​

Each instance has a firewall of its own, on its Firewall tab: the same rules, asserted per path, so one instance can be open to the world while its /web/database is not. Its sub-tabs are Log, Rules, Pre-authentication and Configuration, which holds the Logging card for request bodies first and the Rate limiting card below it. One Save saves all of them together.

From the command line​

An instance's access rules have a command each:

Remote or localagainst pelmo
$ ohm instance access-control list --instance fanes
Remote or localagainst pelmo
$ ohm instance access-control set --instance fanes --path /web/database --policy Block --address 203.0.113.10
Remote or localagainst pelmo
$ ohm instance access-control remove --instance fanes --path /web/database

A rule governs a path prefix. --policy Allow means everyone except what you name; Block means nobody except. --on-block decides what a refused caller actually gets - a not-found page by default, or a maintenance page, a status code, or a redirect. A redirect URL must be a plain http:// or https:// address without spaces, quotes, backslashes, semicolons, braces or a dollar sign; anything else is refused.

Admitting by --hostname resolves the name and keeps it current, which is how a rule follows a dynamic address.

A rule's allowed addresses take a single IPv4 or IPv6 address or a CIDR range each, and nothing else: anything that is not an address or a range is refused, in the console and on the command line. A host name belongs in the rule's host names, not among its addresses. An allowed address saved before 0.24.0 that is not an address or a range is left out of the instance's site from 0.24.0 on; move a host name to the host names of its rule on the instance's Firewall tab.

Odoo's external API​

Every instance carries an Odoo API rule, closed to everyone. It covers the ways a program talks to Odoo rather than a person in a browser: XML-RPC (/xmlrpc/), JSON-RPC (/jsonrpc), the bearer-token JSON APIs (/json/1/ on Odoo 18, /json/2 from Odoo 19) and the API documentation browser (/doc). The paths of every supported Odoo version are covered at once, so the rule keeps working when an instance is upgraded. The web client, the mobile app and the Documents app's share links do not use these paths and are not affected.

When an integration needs the API, open the rule to that integration's address or country on the instance's Firewall > Rules tab, or to everyone. Switching the rule off, or deleting it, does not open the API: the paths then fall to Default Deny & Log, and a deleted rule comes back closed. Opening it to chosen addresses or countries needs a license that carries access rules, as every rule that names whom it admits does; opening it to everyone, or keeping it closed, does not.

Payment providers' webhooks​

A payment provider tells Odoo that a payment succeeded, failed or was refunded by calling the instance from its own servers: Stripe at /payment/stripe/webhook, Adyen at /payment/adyen/notification, and so on for every provider Odoo supports, plus Paddle at /payment/paddle/webhook for the Antelao sales module. The Payments rule governs exactly those addresses. The pages a buyer opens in their own browser - the pay page, the return from the provider, the processing and confirmation pages - are not part of it and stay with the Odoo rule.

A new instance starts with the Payments rule open to everyone, so restricting the Odoo rule to your office never refuses the providers. That is safe: every provider signs what it sends, and Odoo checks the signature. To narrow it, choose Only the entries below and tick the providers you use:

ProviderWhat the rule admits
StripeThe addresses Stripe publishes for its webhooks
Paddle and Paddle (test)The addresses of Paddle's live and test environments, two separate lists
MollieThe addresses Mollie publishes
AdyenWhatever its host name out.adyen.com resolves to, kept current like any host name

The server reads each provider's list every hour and when it starts, and writes it into the rules that name the provider; when a list changes, the instances that use it are applied by themselves. A read that fails keeps the list read before, so a provider's outage never refuses its own webhooks, and the instance's Health tab warns while a list it relies on cannot be read. PayPal publishes no such list: for PayPal, leave the rule open or list its addresses yourself.

An instance created before this rule existed has none, and its webhooks follow its Odoo rule as before: if that rule admits only your office, the providers are refused too. Add a rule for the Payments component on the instance's Firewall > Rules tab and apply. An exemption on the Pre-authentication tab does not get a provider past the firewall; it only spares it the sign-in. Narrowing the rule to providers needs a license that carries access rules; keeping it open does not.

To ask everyone the rules admit for a user name and password as well, switch on the instance's Pre-authentication.

The server's own uptime check gets through an instance's rules without being listed in them, an address list as much as a country or host-name rule: a plain request for the sign-in page, from inside the server, carrying the check's own name and a value only this server knows. It is not a row in the instance's log. Anything else from inside the server or from a private network - the same request without that value, sent as a form, for another page, or by another program - is judged like any visitor.

After updating to 0.24.0, an instance with firewall rules or pre-authentication can read as refused in the uptime view until its site is rebuilt: repair its Web server site check on the instance's Health tab, or apply the instance's configuration.

Logging per rule​

Every rule has a Log switch. Refused requests are always logged; switch Log off on a busy rule, such as the one for Odoo's own assets, and the requests it admits are left out of the firewall log. The web server's own access log stays complete, so rate limits and Fail2Ban still see every request. Default Deny & Log always logs, and its switch cannot be changed.

Request details and request bodies​

The info button on a row of Decisions shows the whole request as the web server saw it: method, path with its query, status, protocol, response size, response time, user agent and referrer. The export carries the same.

Request bodies, in the Logging card on Firewall > Configuration, adds what was sent, and it is off by default:

  • Bodies up to 4 KB are kept. A longer body, a file upload, and a request refused before it reached Odoo have none.
  • Mask sensitive data, on by default, replaces the value of every field whose name suggests a secret - passwords, tokens, keys, session and card numbers - before the body leaves the server.
  • A body carries what your visitors typed: their names, addresses and orders. Switch it on to investigate, and off again when you are done.
The server's own access log is not masked

Masking applies to the firewall log, its export and support bundles. The web server's access log on the server keeps a body exactly as it arrived, readable by root only and rotated like every access log.

Rows from before the update, and rows decided by the access check before the request was passed on, have no details.

Reading an instance's refusal log​

A path whose rules need the management API (a host name, a country, pre-authentication) is judged by asking it, and a request it cannot judge is refused - never let through. The Reason column says which kind of refusal a row is:

ReasonWhat happened
The rule's own reason, or its address is not on the allow-listThe rules refused the caller
refused closed: the access check could not answer (HTTP 502)Nothing was judged: the management API was not answering, for example while an update restarted it. The caller would be refused whatever the rules said
refused by the web server (HTTP ...)The site answered with that status without asking the rules, for example a rate limit

An allowed row can carry a reason too. The firewall had no part in either of these; the instance's Odoo log for the same minute says why:

ReasonWhat happened
admitted; Odoo could not be reached (HTTP 502) (or 503, 504)The rules let the caller in and Odoo did not answer: it was restarting, being replaced during an update, or the instance was in maintenance. The visitor saw the instance's maintenance page
admitted; Odoo itself answered HTTP 500 (or 503, 403)The rules let the caller in and Odoo answered with an error of its own, or refused the request itself

A visitor the rules let in always gets the maintenance page while Odoo is down or the instance is in maintenance, as on an instance without such rules; a visitor the rules refuse, or one they could not judge, keeps getting the instance's own refusal.

A caller who leaves before the site answers is not a refusal and is not in the log. An instance whose configuration has not been applied since the last update writes less into these rows: a refusal that closed the connection is then put down to the rules, whatever caused it. Apply the instance's configuration to get the full reasons.

Location rules and GeoIP​

Country and continent rules need somewhere to look an address up, which is what Security > GeoIP is for: a MaxMind account, the GeoLite2 databases the ohm-geoip service downloads with it, and a by-hand lookup for when you want to know what a rule would decide. Without it, location rules have nothing to match against.

ohm-geoip is Antelao GeoIP, a product in its own right, running here as a container of this server's stack. Its own manual, under Related below, covers the credential, the editions it keeps current, and why a lapse there can never switch this server's rules off.

Trusted proxies, or every rule describes the wrong caller​

If a load balancer or CDN terminates the visitor's connection, tell the product about it under Configuration, or at install time with ANTELAO_INSTALL_TRUSTED_PROXIES. An instance's own trusted proxies are set by an Administrator only: they decide which address Fail2Ban and CrowdSec ban for every instance on the server. A range wider than /8 for IPv4 or /32 for IPv6 is refused.

Getting this wrong is silent

Without it, every firewall rule, every rate limit and every log entry describes the proxy rather than the visitor. Fail2Ban bans the proxy, the firewall admits everyone behind it, and nothing anywhere reports a problem. See the same warning from the other side in Intrusion prevention, and Trusted proxies for the setting itself.

Exposure​

The management ports are not behind the web server

Only the Odoo instances, on ports 80 and 443, are behind the web server and get its protection. The ports below answer on the network directly. Every port, what binds it and what the firewall script does with it is in the port table.

PortProtocolBound toFirewall scriptServiceWhat it is
5202HTTPSEvery interfaceYour addressesohm-agentThe host agent's page and API. Protected by its key and by who may reach the agent
5312HTTPSEvery interfaceYour addressesohm-apiThe management API
5322HTTPSEvery interfaceYour addressesohm-webThe console
5323HTTPSEvery interfaceYour addressesohm-webThe browser editor an instance's Code tab opens, with the console's certificate. Allow it wherever you allow 5322
5511HTTPEvery interfaceYour addressesohm-metabaseAnalytics, once installed
5512HTTPSEvery interfaceYour addressesohm-metabaseAnalytics, with the management certificate
9000HTTPEvery interfaceYour addressesohm-portainerPortainer, while installed
9443HTTPSEvery interfaceYour addressesohm-portainerPortainer, while installed
2222SSHEvery interfaceYour addressesohm-sshThe SSH gateway for developers, while it is switched on. The port can be moved; the script names 2222
5680-5779TCPEvery interfaceYour addressesohm-<id>An instance's debugger, while remote debugging is on for it
6680-6779HTTPEvery interfaceYour addressesohm-<id>An instance's own HTTP port, while remote debugging is on for it

Keep them closed to everyone but your administrators: with the firewall script, a provider firewall that admits the same addresses, or a VPN. A Portainer instance open to the internet is a root shell on your Docker host.

The installer publishes a ready-made UFW ruleset beside itself that opens 80 and 443 to the world and restricts the management ports to an allow-list of your addresses. See Restricting the management ports.

pgAdmin's own port answers on the server itself only, whatever a firewall allows; reach it through an SSH tunnel, or publish it under /pgadmin:

ssh -L 8081:127.0.0.1:8081 admin@your-server

and open http://127.0.0.1:8081 on your own machine.

The agent is the way back in when the console is down, and its key is its protection. ANTELAO_BOOT_ALLOW in /etc/antelao/agent.env says from which addresses the key may be presented; its default, and how to change it, are in Who may reach the agent.

Portainer​

Portainer is a web console for Docker. It is optional and is not installed unless you choose it, because it holds the host's Docker socket.

Portainer controls the whole server

Whoever signs in to Portainer, or opens a console in its container, controls every container on the server and the server itself. Install it only for as long as you need it, keep 9000 and 9443 closed to everyone but your administrators, and keep 8000 closed altogether. Portainer listens on 8000 on every address for its Edge agents, and the product does not use it.

  • Install it on Core services with Install Portainer, which asks first with that warning, or with ohm server portainer install, which asks the same.
  • Remove it with Remove Portainer, or ohm server portainer remove. Its data and admin password are kept, so installing it again brings back the same Portainer.
  • While it is installed, every ohm command warns about it on standard error, so scripts that read a command's output are unaffected.
  • A server install, an update or a repair leaves Portainer as you chose it: installed only when you installed it.

Both are administrator actions, queued like every other change; the Jobs page shows the install or the removal.