TCP Wrappers: why address-based access control breaks
On a production node, sshd, the DBMS, and the message queue broker listen on all interfaces, while address-based access restriction rests on a few lines in hosts.allow added under the previous addressing scheme.
Formally, TCP Wrappers are enabled; in practice, access control to services does not reflect the topology: the old office range remains in the allowed-host list, the contractor's address was added manually and not revoked, and the new subnet never made it into the rules.
The mechanism works at the daemon level: before a connection is processed, the client address is checked against hosts.allow and hosts.deny, and the decision is made by the first matching line.
Hence the cause of failures that is most often overlooked: support for the wrapper library was stripped from current builds, and the service accepts everyone even though the files are filled in.
This is checked not by eye but by running a connection from an external address and analyzing which process holds the port.
The second cause is inconsistent access rules. They are edited during an incident, mask syntax in adjacent lines is interpreted differently, some services are closed at the hosts.deny level, and others only by edge filtering.
The same address ends up allowed in one file and denied in another, and the result can only be read by a full enumeration. At the same time, the files are not versioned: there is no revision for rolling back an edit and no log showing who opened access and why.
We analyze how this is closed: from inventorying listening ports and addresses to moving rules to where they are checked before the service starts, not after.
What managed access at the service level gives
The TCP Wrappers wrapper sits between the incoming connection and the service itself: the daemon checks the client address against the rules in /etc/hosts.allow and /etc/hosts.deny and only then hands control to the application.
The check occurs before the service begins parsing the request, so requests are cut off at entry, not after authentication.
The parsing order is fixed—first allows, then denies. The baseline "deny everything" line in the deny file closes everything not explicitly listed, while the allowlist describes only the necessary subnets and hosts. The access policy remains readable: a dozen lines explain who is allowed to access which services.
The effect is visible in perimeter metrics: scans and attempts to connect to foreign services do not reach the application, do not consume its resources, and do not generate false monitoring alerts.
The attack surface is reduced to explicitly listed addresses, and log noise is cut off along with the requests.
The time to change rules is predictable: a file edit applies to the next connection, and no service restart is needed.
Adding a new host to the allowlist takes a couple of minutes—the engineer adds a line, checks the connection, and records the change without affecting other services.
The wrapper works only for services built with its support, and it does not replace a firewall but complements it at the level of specific services. In practice, it is used where perimeter rules are too coarse and a precise filter for an individual daemon is needed.
Methods of restricting access: comparison of approaches
Access is restricted at four levels, and the choice of level determines what exactly you control: the service, the host, application entry, or segment reachability.
TCP Wrappers work at the service level: the rule "allow the subnet, deny the rest" is applied before request processing, but only to daemons built with support for the wrapper library.
A host firewall filters traffic before the service and does not depend on its build, but it does not distinguish users—only address and port. A reverse proxy checks requests at application entry, but protects only what goes through it.
Network segmentation removes the route to the host itself, so a service configuration error no longer opens access.
| Option | When suitable | Limitations |
|---|---|---|
| Service-level rules (TCP Wrappers) | Precisely close a specific daemon by address or subnet without changing routes | Only services with wrapper support; configuration is edited on each node |
| Host firewall | A single set of rules for all services on the host, including non-standard ones | Does not distinguish users; rules are duplicated across hosts |
| Restrictions on a reverse proxy | Access to web applications: address list, rate limiting, checks at entry | Covers only traffic through the proxy; internal services remain open |
| Network segmentation | Isolation of environments and least-privilege access | More expensive to change: rebuilding routes and policies instead of editing one file |
In practice, the modes are combined: segmentation cuts off unnecessary routes, a host firewall closes ports, and service-level rules remain a quick, precise edit for an individual daemon during migration.
Procedure for implementing access rules
Rules in hosts.allow and hosts.deny are read on every connection, so an incorrect line can close access before you notice it. Implementation proceeds in stages—from service inventory to handover to operations.
- Service inventory. We compile a list of daemons listening on the network and check which of them are built with wrapper support: for the rest, the rules will not apply, and access will remain open despite a formally configured deny.
- Collecting the traffic picture. Using logs and active connections, we record actual "service—address" pairs: who connects, from where, and how often. This is the basis of the access scheme instead of assumptions.
- Designing the access scheme. We describe permissions on the principle of "everything that is not allowed is prohibited": specific pairs in allow, a baseline block in deny. We take into account the processing order—the first match wins.
- Configuring and checking rules. We run the configuration through tcpdmatch on control "service—address" pairs and see how the denial is reflected in the log.
- Controlled enablement. We introduce the rules in an agreed window, monitor denials in logs, and keep a ready rollback: restoring previous files takes minutes.
- Handover to operations. The files go into the configuration management system, and the access scheme and procedure for revising it are recorded in the documentation.
The output is a minimal set of allowed pairs and a clear regulation for changing it without risking cutting off necessary traffic.
Configurations, connection maps, and access regulations
The package is handed over together with access to the infrastructure and reviewed during handover: the customer's engineer must be able to add an address to the exception list themselves and understand why a change did not take effect.
Below is what remains with you after handover, rather than staying in correspondence with the contractor.
- Working access rules — files with allowed and denied connections by node and service, with a comment for each entry: who added it, for what task, and until what date it is valid.
- Allowed-connection map — a one-page map: which nodes access which services and on which ports. It is used when analyzing incidents and when onboarding new services.
- Change procedure — the approval procedure for an edit: request, check on a test environment, application window, and a pre-described rollback to the previous version of the rules.
- On-call shift instructions — step-by-step analysis of typical cases: a service is unavailable for some clients, a denial by address appears in logs, suspicion of a false block. With an indication of which commands to run and where to look.
- Rule review procedure — once a quarter we compare the list with actual traffic, remove entries nobody uses, and confirm the remaining ones with service owners.
- Responsibility map — who owns each rule and whom to contact when there is a discrepancy: first the service owner, then the on-call engineer, then us under the agreed support level.
The documents live in the same repository as the rules themselves—during rollback, both the configuration and its description are restored.
During handover, we run a scenario: the on-call engineer removes the block and restores it back without our prompting, following the instructions.
Case study: how access to administrative services was closed
In one project, administrative interfaces—the admin panel, metrics page, database replication port—listened on all addresses.
Filtering relied on the provider's rules and a login password, and the verification logic was spread between the network and the application: no one could answer the question "who exactly has access." At the same time, brute-force attempts from external addresses were consistently logged—several hundred per day.
Access separation was moved to the service level itself via TCP Wrappers. In hosts.allow, we listed trusted segments—the office network and the bastion address; everything else went into hosts.deny with an explicit deny and a log entry.
The decision is made at connection time, before authentication, so it did not require changes to service code or reconfiguration of load balancers.
Before and after
- Denied requests from untrusted addresses: about 400 per day before the work—now the flow is cut off at entry, and there are no legitimate denials in the rules.
- Access to administrative ports: from any addresses—to two trusted segments and the bastion.
- Editing an access rule: previously dozens of minutes to rebuild the network filter and get approval, now 3–5 minutes for a line in hosts.allow.
The rule became fully visible in a single version-controlled file—along with it came a change history and a clear rollback. Over a quarter of observation, no production service lost access, and the denial records produced a list of addresses worth checking first.
How do TCP Wrappers differ from firewall rules?
A firewall and TCP Wrappers operate at different levels, and that is where all the differences come from. A host filter looks at packet headers—source address, port, interface—and drops traffic before the service sees it.
The wrapper that Wietse Venema added to system services is embedded into the daemon itself: the decision is made after the connection is established, when the host name is already known.
The practical difference is in matching flexibility. The wrapper works with names: hosts.allow and hosts.deny files are read in order, the first match wins, patterns cover entire domains, and a line with the name of the triggered rule goes into the system log. A firewall knows nothing about the domain—only address and port.
The boundaries of applicability are stricter than they seem. The wrapper sees only those services built with its support: a request to a daemon without such a link will pass by, and it does not parse datagram traffic or service messages at all.
It will not stop channel overload either—the connection consumes a resource before the rule takes effect.
Therefore they are used together, not instead of one another. The firewall cuts off by address and port at entry—a cheap boundary that keeps junk traffic away from the service. The wrapper adds a layer where selection by host name and a log trail for incident analysis are needed. The order is exactly this: first packet filtering, then a decision at the service level.
You should also look at the daemon itself separately: some modern services maintain their own access lists and restrict clients without external wrappers.
Then the third layer only complicates finding the cause of a denial—the rule is written in three places, and only one triggers.
Technical concerns: answering common questions
TCP Wrappers checks access by service name and client address before the connection is handed to the daemon. This gives rise to the main questions: when an edit takes effect, who reads the list first, and what to do if a new rule closed access more strictly than planned.
Below are four doubts that most often come up when handing configuration over to operations.
Do I need to restart the service after editing the rules?
No. Access lists are read on every incoming connection, so an edit applies to new connections immediately, while already established sessions continue to work.
A restart is needed only for daemons that cache the check result at startup—we note such cases separately in the documentation.
In what order are the rules parsed?
The deny list is checked in full first: a match closes access immediately, and the allow list is no longer considered. If there are no matches, the allow list is evaluated, and its absence is treated as a deny.
The order of lines within a file does not matter, so we write the final "for everyone" rule explicitly.
How do containers behave?
The check is performed by source address, and in an isolated environment the gateway address is visible rather than the client's. When ports are forwarded, a rule by external address will not work—it will work by internal address.
It is more reliable to keep access restriction to a service in a cluster at the entry point, and leave the lists as a second boundary.
How to roll back changes if access is lost?
Before editing, we keep a copy of the files and a separate session that has already passed verification—it lives as long as the connection is open. Returning to the previous version takes minutes, and a run on a node copy shows the effect before deployment to production.
Let's discuss the access control task
To make the work estimate accurate and the project start free of downtime for clarifications, we need only the initial data on the current access perimeter.
Nothing additional needs to be prepared: what administrators already have and what is in the configuration repository is enough.
- List of services to which access is restricted (remote console, mail, file sharing, databases) and how they are started—directly or under a super-server.
- Current
/etc/hosts.allowand/etc/hosts.denyfiles: if rules are already written, it is immediately clear what is inherited and what conflicts. - List of trusted networks and addresses—office, branches, remote access pools, contractor addresses, and monitoring systems.
- How addresses are issued to clients: statically or from a dynamic pool. This determines whether to set a rule by address, by subnet, or by host name.
- Traffic path diagram: where the load balancer is, where the reverse proxy is, and whether address translation exists at the perimeter.
- Requirements for logging denials: where events are written, retention period, and whether export to a centralized log collection is needed.
- Permissible work window and procedure for returning to previous rules—if we apply edits on the production environment.
This is enough to discuss the task substantively and come back with an estimate and work plan: the procedure for changing rules, checkpoints at each step, and a way to roll back the configuration.
If some data is unavailable, we start with an inventory—our engineers will collect it themselves and agree on the list of services. Describe your situation: we will provide an estimate, timelines, and a project start plan.







