A service that is needed for ten minutes does not have to be reachable for the other 1,430 minutes of the day. Temporary exposure reduces the time available for scanning, guessing, and exploiting a mistake—but only when “temporary” is enforced by the system rather than by memory.
The useful model is a short lease: an authenticated request opens a narrowly defined path, accepted activity may renew its idle deadline, and an independent expiry closes it again. The lease has a maximum lifetime even when activity continues. Every transition is observable from outside.
Define what “closed” means
A process, a socket, and a public path are different layers. A daemon may remain running and a socket may remain bound while an edge rule makes the service unreachable. Conversely, stopping a process does not prove that another listener, proxy, or default route did not take its place.
- Process state
- Whether the program is running and ready to serve
- Listener state
- Whether a local socket is accepting traffic on an interface
- Reachability state
- Whether an external client can reach the intended path
- Authorization state
- Whether a verified client currently holds a valid lease
- Session state
- Whether existing work may drain after new access is closed
Choose one layer as the authority for opening and closing, then test the result at the public boundary. If the requirement is “not reachable from the internet,” an outside connection attempt is the evidence that matters. A green process check is not enough.
Closed by default is an externally observed property, not a description of one process.
Put time in the access contract
An idle timeout alone is not a complete boundary. A busy or stuck client can keep it alive forever. Use two deadlines: a short idle deadline that moves only after valid activity, and an absolute deadline that never moves. The lease closes when either one arrives.
- Scope
- The smallest service, transport, source, and destination the request needs
- Opened at
- The server-observed start time and the event that authorized it
- Idle deadline
- The next expiry after the last accepted activity
- Absolute deadline
- The non-renewable end of the lease
- Owner
- The identity and reason attached to the request
- Close result
- The time, reason, and outside verification of closure
Keep the server authoritative. A client may ask for a duration, but it should not set its own expiry or declare itself active. Clamp requests to a documented maximum, store the granted deadlines, and return those exact deadlines to the client so the two sides can display the same expectation.
Renew only verified activity
Traffic is not the same as use. Internet noise, failed handshakes, malformed packets, health checks, and repeated authentication failures must not keep a lease open. Renewal should follow an event that the protected service has already accepted as belonging to the authorized session.
- Authenticate before opening. Validate identity, freshness, replay protection, and requested scope before changing reachability.
- Renew after acceptance. Move the idle deadline only after the service accepts meaningful traffic from the leased identity.
- Use a server clock. Measure durations with a monotonic clock so wall-clock corrections cannot stretch or shorten a lease unexpectedly.
- Separate failure counters. Record rejected traffic for detection, but never count it as activity.
- Keep an independent closer. Expiry must continue even if the request handler or user interface disappears.
Connection-oriented transports offer session events that can help identify accepted work, but an established connection may remain after new connections are blocked. Connectionless transports need an application-level session or authenticated packet rule because there is no connection teardown to observe. In both cases, renewal belongs to the authenticated application boundary—not to the raw packet counter.
Close in the right order
Opening and closing are short workflows, not single toggles. The order matters when two requests arrive together, a session is still active, or the controller restarts midway through a transition.
- Stop issuing new leases. Serialize the transition for the affected scope so a concurrent request cannot reopen it accidentally.
- Revoke new external reachability. Remove the public path before performing slower cleanup behind it.
- Mark the lease closed. Persist the close reason and deadline that won the race.
- Drain deliberately. If existing work is allowed to finish, give it a small, bounded grace period; otherwise terminate it immediately.
- Return the local service to rest. Stop an on-demand listener or leave a resident listener isolated, according to the chosen authority.
- Verify from outside. Confirm that a new client can no longer reach the path and record the result.
Closing reachability before stopping the service narrows the race window and keeps partial cleanup from becoming public. Reopening should create a new lease with a new identity and deadlines; it should not silently resurrect the previous one.
Make failures close the path
The design is only closed by default if its failure modes are closed too. Treat the lease as disposable state. After a restart, an absent, corrupt, ambiguous, or expired record should produce a closed path.
- Controller stops
- A host-level expiry still removes the granted reachability
- Host restarts
- The path stays closed; fresh authorization is required even if time remained
- Clock changes
- Monotonic durations remain authoritative for the running lease
- State is unreadable
- The path stays closed and the error is reported for repair
- Close verification fails
- The system raises an alert; it does not claim success or extend the lease
Do not make the user interface the watchdog. A closed laptop, expired browser session, or failed notification must not affect the deadline. The closer belongs beside the enforcement point and should need less state than the opener.
The safest lease is one that the opener cannot keep alive by crashing.
Verify the whole lifecycle
A good test begins closed and ends closed. Run it from a network that does not share the enforcement point, and preserve the observations with the same discipline as a public-service preflight.
- Cold state: the service is not externally reachable before a lease exists.
- Valid open: the smallest requested scope becomes reachable and unrelated paths stay closed.
- Invalid open: stale, replayed, malformed, and unauthorized requests change nothing.
- Idle expiry: the path closes after accepted activity stops, despite unrelated traffic.
- Absolute expiry: continuous valid activity cannot exceed the maximum lease.
- Controller failure: terminating the opener does not leave the path reachable.
- Restart: reboot or service restart returns to the documented closed state.
- Concurrency: simultaneous open and close requests produce one recorded outcome.
- Outside closure: a fresh client fails after close, while unrelated public services still behave normally.
Keep one compact lifecycle record: requested scope, granted deadlines, opening identity, last accepted activity, close reason, enforcement result, and outside verification. Add it to the system’s one-page service record so a future operator can tell whether the mechanism is merely running or actually enforcing the intended boundary.
Temporary exposure is useful when the lifecycle is more reliable than human memory. Make the lease explicit, renew only trusted work, give it an immovable end, and verify closure where an untrusted client would see it.