# domainexpiry

Source: https://www.pingtower.com/docs/addons/domainexpiry/

Read a registered domain's expiry date from its registry, over RDAP or WHOIS, and warn 30 days before it lapses.

A lapsed domain takes every service under it down at once, and the registry publishes the date months ahead. This check reads that date from the registry, never from the domain's own servers. It reports the days left on every run, is **degraded** inside `warn_before`, and is **down** once the domain has expired.

It runs on Pingtower's servers as a [hosted check](/docs/service/checks/) and on your own agents. The answer is the same from everywhere, and registries rate-limit by address, so a check runs from one place only. Name one source, or none and Pingtower picks one. When Pingtower runs it, its interval defaults to once a day and its timeout to 30 seconds. See [Domain expiry runs from one place](/docs/service/checks/#domain-expiry-runs-from-one-place).

## Parameters

| Parameter | Default | Meaning |
| --- | --- | --- |
| `domain`<span class="req">REQUIRED</span> | | The domain to watch, such as `example.com`. |
| `warn_before` | `720h` (30 days) | Time left at which the check is **degraded**. |

The check watches the registered domain, the name one level below its public suffix: `www.example.com` is watched as `example.com`, and `example.co.uk` stays itself. A name under a privately run suffix is watched as the registered domain above it, so `acme.github.io` is watched as `github.io`. Write an internationalised name in its `xn--` form. An IP address or a bare public suffix is refused.

## How it finds the date

1. IANA's RDAP bootstrap names the RDAP service for the domain's top-level domain. The agent fetches it once a day.
2. The registry's RDAP record gives the expiration date. When the record links the registrar's own RDAP record, that is read too, and the earlier date is reported. A gTLD registry renews automatically at expiry, so its date can run a year ahead of what the registrant has paid for.
3. A top-level domain with no RDAP service is read over WHOIS, for the registries whose expiry line the check knows: `.io`, `.me` and `.se`.

## Status

| Result | Status |
| --- | --- |
| More than `warn_before` left | **up**: `domain registered for 241d (2027-05-31)` |
| `warn_before` or less left | **degraded**: `domain expires in 12d (2026-10-14)` |
| Expired | **down**: `domain expired 3d ago (2026-09-29)` |
| The registry holds no such domain | **down**: `example.com is not registered` |

What the check can't learn is never reported as down. When the registry publishes no expiry date, or the top-level domain has neither RDAP nor a WHOIS format the check reads, the run is a check error tagged `status:error` with the key `reason` set to `no_expiry_published`. When the registry can't be reached, `reason` is `registry_unreachable`. Neither votes toward an alert.

## Measurements

| Key | Meaning |
| --- | --- |
| `days_until_expiry` | Days left, negative once expired |
| `lookup_ms` | The whole lookup |
| `source` | Label: `rdap` or `whois` |
| `expiry_from` | Label: `registry` or `registrar`, whose date was reported |

The check needs addons agent 0.4.0 or later; an older agent reports it as an unknown addon.

## Example

```yaml
- id: acme-domain
  addon: domainexpiry
  interval: 24h
  timeout: 30s
  with:
    domain: acme.example
    warn_before: 1440h       # 60 days
```

## A rule for it

```json
{"name":"domain-expiring","match":{"tags":["addon:domainexpiry"]},
 "conditions":[{"key":"days_until_expiry","op":"lte","value":14}],
 "identifier_keys":["check"],"renotify_minutes":1440}
```
