Skip to content

Conversation

@ldez
Copy link
Member

@ldez ldez commented Sep 21, 2025

  • adds a description to your PR
  • have a homogeneous design with the other providers
  • add tests (units)
  • add tests ("live")
  • add a provider descriptor
  • generate CLI help, documentation, and readme.
  • be able to do: (and put the output of this command to a comment)
    make build
    rm -rf .lego
    
    HOSTINGER_API_TOKEN="xxx" \
    lego --email [email protected] --dns hostinger -d '*.example.com' -d example.com -s https://acme-staging-v02.api.letsencrypt.org/directory run
    Note that the wildcard domain is important.
  • pass the linter
  • do go mod tidy

Closes #2650
Related to #2622

Ping @tafaust, can you run the command (with your domain, email, credentials, etc.)?

How to test this PR?
  1. You need Go
  2. Check out the PR:
    git clone https://github.com/ldez/lego.git
    cd lego
    git checkout feat/dns/hostinger
  3. Compile lego:
    • if you have make: make build
    • if you don't have make: go build -o dist/lego ./cmd/lego
  4. Run the following command with your information (email, domain, credentials):
    HOSTINGER_API_TOKEN="xxx" \
    ./dist/lego --email [email protected] --dns hostinger -d '*.example.com' -d example.com  -s https://acme-staging-v02.api.letsencrypt.org/directory run
    The wildcard domain is important
  5. Before each run of the command, you should clean your local environment:
    rm -rf .lego

@ldez ldez added area/dnsprovider waiting-for/user-tests Need users to test functionality labels Sep 21, 2025
@ldez ldez changed the title Feat/dns/hostinger Add DNS provider for Hostinger Sep 21, 2025
@tafaust
Copy link

tafaust commented Sep 21, 2025

I will latest test tomorrow, there was a domain forward with my test domain. It timed out on acme: Waiting for DNS record propagation. in that scenario. I will post back latest tomorrow.

@tafaust
Copy link

tafaust commented Sep 22, 2025

$ HOSTINGER_API_TOKEN="xxx" \
  ./dist/lego --email [email protected] --dns hostinger -d '*.my-domain.de' -d my-domain.de  -s https://acme-staging-v02.api.letsencrypt.org/directory run
2025/09/22 09:25:25 No key found for account [email protected]. Generating a P256 key.
2025/09/22 09:25:25 Saved key to /Users/thomas/Code/lego/.lego/accounts/acme-staging-v02.api.letsencrypt.org/[email protected]/keys/[email protected]
2025/09/22 09:25:27 Please review the TOS at https://letsencrypt.org/documents/LE-SA-v1.5-February-24-2025.pdf
Do you accept the TOS? Y/n
y
2025/09/22 09:25:31 [INFO] acme: Registering account for [email protected]
!!!! HEADS UP !!!!

Your account credentials have been saved in your
configuration directory at "/Users/thomas/Code/lego/.lego/accounts".

You should make a secure backup of this folder now. This
configuration directory will also contain certificates and
private keys obtained from the ACME server so making regular
backups of this folder is ideal.
2025/09/22 09:25:31 [INFO] [*.my-domain.de, my-domain.de] acme: Obtaining bundled SAN certificate
2025/09/22 09:25:32 [INFO] [*.my-domain.de] AuthURL: https://acme-staging-v02.api.letsencrypt.org/acme/authz/229784594/19427347394
2025/09/22 09:25:32 [INFO] [my-domain.de] AuthURL: https://acme-staging-v02.api.letsencrypt.org/acme/authz/229784594/19427347404
2025/09/22 09:25:32 [INFO] [*.my-domain.de] acme: use dns-01 solver
2025/09/22 09:25:32 [INFO] [my-domain.de] acme: Could not find solver for: tls-alpn-01
2025/09/22 09:25:32 [INFO] [my-domain.de] acme: Could not find solver for: http-01
2025/09/22 09:25:32 [INFO] [my-domain.de] acme: use dns-01 solver
2025/09/22 09:25:32 [INFO] [*.my-domain.de] acme: Preparing to solve DNS-01
2025/09/22 09:25:55 [INFO] [my-domain.de] acme: Preparing to solve DNS-01
2025/09/22 09:26:07 [INFO] [*.my-domain.de] acme: Trying to solve DNS-01
2025/09/22 09:26:17 [INFO] [*.my-domain.de] acme: Checking DNS record propagation. [nameservers=10.0.0.10:53,[fded:38c5:4ff:0:e208:55ff:fe94:9390]:53,[2a01:41e3:274a:fa00:e208:55ff:fe94:9390]:53,[2a01:41e3:27a9:c700:e208:55ff:fe94:9390]:53]
2025/09/22 09:26:19 [INFO] Wait for propagation [timeout: 1m0s, interval: 2s]
2025/09/22 09:26:29 [INFO] [*.my-domain.de] acme: Waiting for DNS record propagation.
2025/09/22 09:26:41 [INFO] [*.my-domain.de] acme: Waiting for DNS record propagation.
2025/09/22 09:26:53 [INFO] [*.my-domain.de] acme: Waiting for DNS record propagation.
2025/09/22 09:27:05 [INFO] [*.my-domain.de] acme: Waiting for DNS record propagation.
2025/09/22 09:27:17 [INFO] [*.my-domain.de] acme: Waiting for DNS record propagation.
2025/09/22 09:27:19 [INFO] [my-domain.de] acme: Trying to solve DNS-01
2025/09/22 09:27:30 [INFO] [my-domain.de] acme: Checking DNS record propagation. [nameservers=10.0.0.10:53,[fded:38c5:4ff:0:e208:55ff:fe94:9390]:53,[2a01:41e3:274a:fa00:e208:55ff:fe94:9390]:53,[2a01:41e3:27a9:c700:e208:55ff:fe94:9390]:53]
2025/09/22 09:27:32 [INFO] Wait for propagation [timeout: 1m0s, interval: 2s]
2025/09/22 09:27:42 [INFO] [my-domain.de] acme: Waiting for DNS record propagation.
2025/09/22 09:27:54 [INFO] [my-domain.de] acme: Waiting for DNS record propagation.
2025/09/22 09:28:06 [INFO] [my-domain.de] acme: Waiting for DNS record propagation.
2025/09/22 09:28:18 [INFO] [my-domain.de] acme: Waiting for DNS record propagation.
2025/09/22 09:28:30 [INFO] [my-domain.de] acme: Waiting for DNS record propagation.
2025/09/22 09:28:32 [INFO] [*.my-domain.de] acme: Cleaning DNS-01 challenge
2025/09/22 09:28:44 [INFO] [my-domain.de] acme: Cleaning DNS-01 challenge
2025/09/22 09:28:57 [INFO] Deactivating auth: https://acme-staging-v02.api.letsencrypt.org/acme/authz/229784594/19427347394
2025/09/22 09:28:57 [INFO] Deactivating auth: https://acme-staging-v02.api.letsencrypt.org/acme/authz/229784594/19427347404
2025/09/22 09:28:58 Could not obtain certificates:
	error: one or more domains had a problem:
[*.my-domain.de] propagation: time limit exceeded: last error: authoritative nameservers: NS ns1.dns-parking.com.:53 returned NXDOMAIN for _acme-challenge.my-domain.de.
[my-domain.de] propagation: time limit exceeded: last error: authoritative nameservers: NS ns2.dns-parking.com.:53 returned NXDOMAIN for _acme-challenge.my-domain.de.

is there any way I can further debug this @ldez ?

@tafaust
Copy link

tafaust commented Sep 22, 2025

It looks like no TXT record is added during propagation

@tafaust
Copy link

tafaust commented Sep 22, 2025

Running

curl https://developers.hostinger.com/api/dns/v1/zones/my-domain.de \
  --request PUT \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer xxx' \
  --data '{
  "zone": [
    {
      "name": "_acme-challenge",
      "records": [
        {
          "content": "aaa"
        },
        {
          "content": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY"
        }
      ],
      "ttl": 14400,
      "type": "TXT"
    }
  ]
}'

manually works.

dig +short TXT _acme-challenge.my-domain.de @ns1.dns-parking.com
"aaa"
"ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY"

The Hostinger API does overwrite: true by default.

@tafaust
Copy link

tafaust commented Sep 22, 2025

This patch for Present made the server respond with a certificate:

diff --git a/providers/dns/hostinger/hostinger.go b/providers/dns/hostinger/hostinger.go
index abb91408..8505ea00 100644
--- a/providers/dns/hostinger/hostinger.go
+++ b/providers/dns/hostinger/hostinger.go
@@ -103,28 +103,59 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
 
 	ctx := context.Background()
 
-	recordSets, err := d.client.GetDNSRecords(ctx, dns01.UnFqdn(authZone))
+	zoneName := dns01.UnFqdn(authZone)
+
+	recordSets, err := d.client.GetDNSRecords(ctx, zoneName)
 	if err != nil {
 		return fmt.Errorf("hostinger: get DNS records: %w", err)
 	}
 
-	var newRecordSet []internal.RecordSet
+	hasTXTAtName := false
+	newRecordSets := make([]internal.RecordSet, 0, len(recordSets)+1)
 
-	for _, recordSet := range recordSets {
-		if recordSet.Name == subDomain && recordSet.Type == "TXT" {
-			recordSet.Records = append(recordSet.Records, internal.Record{Content: info.Value})
+	containsValue := func(recs []internal.Record, val string) bool {
+		for _, r := range recs {
+			if r.Content == val {
+				return true
+			}
 		}
+		return false
+	}
 
-		newRecordSet = append(newRecordSet, recordSet)
+	for _, rs := range recordSets {
+		// If this is the TXT set for _acme-challenge, merge the new value
+		if rs.Name == subDomain && rs.Type == "TXT" {
+			hasTXTAtName = true
+			if !containsValue(rs.Records, info.Value) {
+				rs.Records = append(rs.Records, internal.Record{Content: info.Value})
+			}
+			newRecordSets = append(newRecordSets, rs)
+			continue
+		}
+		// Keep every other record set as-is
+		newRecordSets = append(newRecordSets, rs)
 	}
 
-	request := internal.ZoneRequest{
-		Overwrite: false,
-		Zone:      newRecordSet,
+	// If no TXT record set exists yet for this name, create it
+	if !hasTXTAtName {
+		newRecordSets = append(newRecordSets, internal.RecordSet{
+			Name: subDomain,                 // should be "_acme-challenge"
+			Type: "TXT",
+			TTL:  300,                       // FIXME use env var
+			Records: []internal.Record{
+				{Content: info.Value},
+			},
+		})
 	}
 
-	err = d.client.UpdateDNSRecords(ctx, dns01.UnFqdn(authZone), request)
-	if err != nil {
+	req := internal.ZoneRequest{
+		Overwrite: false, 
+		Zone:      newRecordSets,
+	}
+
+	if err := d.client.UpdateDNSRecords(ctx, zoneName, req); err != nil {
 		return fmt.Errorf("hostinger: update DNS records (add): %w", err)
 	}

Feel free to cherry pick as source of motivation. Sorry for my poor golang.

  • create TXT set if missing: name=_acme-challenge, type=TXT, ttl=300 (use env var here), add token
  • merge semantics: append token to existing TXT set (supports apex + wildcard), no overwrite
  • de-dup: don’t re-add the same TXT value if it’s already there
  • preserve everything else: pass through other records unchanged; Overwrite=false
  • small tidy-ups: precompute zoneName, prealloc slice, add comments
  • follow-up: make CleanUp mirror this (same subDomain, remove only matching value)

TL;DR: success + I'm no golang dev :D

@ldez
Copy link
Member Author

ldez commented Sep 22, 2025

Your patch is not related to propagation, so I think you have wrongly interpreted the error.

@ldez
Copy link
Member Author

ldez commented Sep 22, 2025

Can you only increase the propagation timeout HOSTINGER_PROPAGATION_TIMEOUT?

@ldez
Copy link
Member Author

ldez commented Sep 22, 2025

OK, your explanation was not clear, but I think I understand: the TXT record was not created when there is no existing record set for the subdomain.

Can you run my code and give me the log?

@tafaust
Copy link

tafaust commented Sep 22, 2025

aeb1017 fixed it, thanks!

@ldez
Copy link
Member Author

ldez commented Sep 22, 2025

I need the logs.

@tafaust
Copy link

tafaust commented Sep 22, 2025

HOHOSTINGER_API_TOKEN="xxx" \
./dist/lego --email [email protected] --dns hostinger -d '*.my-domain.de' -d my-domain.de -a -s https://acme-staging-v02.api.letsencrypt.org/directory run
2025/09/22 15:06:00 No key found for account [email protected]. Generating a P256 key.
2025/09/22 15:06:00 Saved key to /Users/thomas/Code/lego/.lego/accounts/acme-staging-v02.api.letsencrypt.org/[email protected]/keys/[email protected]
2025/09/22 15:06:00 [INFO] acme: Registering account for [email protected]
!!!! HEADS UP !!!!

Your account credentials have been saved in your
configuration directory at "/Users/thomas/Code/lego/.lego/accounts".

You should make a secure backup of this folder now. This
configuration directory will also contain certificates and
private keys obtained from the ACME server so making regular
backups of this folder is ideal.
2025/09/22 15:06:01 [INFO] [*.my-domain.de, my-domain.de] acme: Obtaining bundled SAN certificate
2025/09/22 15:06:02 [INFO] [*.my-domain.de] AuthURL: https://acme-staging-v02.api.letsencrypt.org/acme/authz/229840674/19430372334
2025/09/22 15:06:02 [INFO] [my-domain.de] AuthURL: https://acme-staging-v02.api.letsencrypt.org/acme/authz/229840674/19430372344
2025/09/22 15:06:02 [INFO] [*.my-domain.de] acme: use dns-01 solver
2025/09/22 15:06:02 [INFO] [my-domain.de] acme: Could not find solver for: tls-alpn-01
2025/09/22 15:06:02 [INFO] [my-domain.de] acme: Could not find solver for: http-01
2025/09/22 15:06:02 [INFO] [my-domain.de] acme: use dns-01 solver
2025/09/22 15:06:02 [INFO] [*.my-domain.de] acme: Preparing to solve DNS-01
2025/09/22 15:06:24 [INFO] [my-domain.de] acme: Preparing to solve DNS-01
2025/09/22 15:06:39 [INFO] [*.my-domain.de] acme: Trying to solve DNS-01
2025/09/22 15:06:49 [INFO] [*.my-domain.de] acme: Checking DNS record propagation. [nameservers=10.0.0.10:53,[fded:38c5:4ff:0:e208:55ff:fe94:9390]:53,[2a01:41e3:274a:fa00:e208:55ff:fe94:9390]:53,[2a01:41e3:27a9:c700:e208:55ff:fe94:9390]:53]
2025/09/22 15:06:51 [INFO] Wait for propagation [timeout: 1m0s, interval: 2s]
2025/09/22 15:06:55 [INFO] [*.my-domain.de] The server validated our request
2025/09/22 15:06:55 [INFO] [my-domain.de] acme: Trying to solve DNS-01
2025/09/22 15:07:05 [INFO] [my-domain.de] acme: Checking DNS record propagation. [nameservers=10.0.0.10:53,[fded:38c5:4ff:0:e208:55ff:fe94:9390]:53,[2a01:41e3:274a:fa00:e208:55ff:fe94:9390]:53,[2a01:41e3:27a9:c700:e208:55ff:fe94:9390]:53]
2025/09/22 15:07:07 [INFO] Wait for propagation [timeout: 1m0s, interval: 2s]
2025/09/22 15:07:11 [INFO] [my-domain.de] The server validated our request
2025/09/22 15:07:11 [INFO] [*.my-domain.de] acme: Cleaning DNS-01 challenge
2025/09/22 15:07:21 [INFO] [my-domain.de] acme: Cleaning DNS-01 challenge
2025/09/22 15:07:31 [INFO] [*.my-domain.de, my-domain.de] acme: Validations succeeded; requesting certificates
2025/09/22 15:07:32 [INFO] Wait for certificate [timeout: 30s, interval: 500ms]
2025/09/22 15:07:34 [INFO] [*.my-domain.de] Server responded with a certificate.

Here are the logs

@ldez ldez removed the waiting-for/user-tests Need users to test functionality label Sep 22, 2025
@ldez ldez added this to the unreleased milestone Sep 22, 2025
@ldez ldez marked this pull request as ready for review September 22, 2025 13:16
@ldez ldez requested a review from dmke September 22, 2025 13:19
Copy link
Member

@dmke dmke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@ldez ldez merged commit f432d21 into go-acme:master Sep 22, 2025
11 of 14 checks passed
@ldez ldez deleted the feat/dns/hostinger branch September 22, 2025 15:56
@ldez ldez changed the title Add DNS provider for Hostinger Add DNS provider for Hostinger Oct 16, 2025
@ldez ldez modified the milestones: unreleased, v4.27 Oct 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

Support for provider: Hostinger

3 participants