diff --git a/README.md b/README.md
index 3b03a739..d3bac170 100644
--- a/README.md
+++ b/README.md
@@ -62,11 +62,11 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns).
 | [Manual](https://go-acme.github.io/lego/dns/manual/)                            | [MyDNS.jp](https://go-acme.github.io/lego/dns/mydnsjp/)                         | [MythicBeasts](https://go-acme.github.io/lego/dns/mythicbeasts/)                | [Name.com](https://go-acme.github.io/lego/dns/namedotcom/)                      |
 | [Namecheap](https://go-acme.github.io/lego/dns/namecheap/)                      | [Namesilo](https://go-acme.github.io/lego/dns/namesilo/)                        | [Netcup](https://go-acme.github.io/lego/dns/netcup/)                            | [Netlify](https://go-acme.github.io/lego/dns/netlify/)                          |
 | [NIFCloud](https://go-acme.github.io/lego/dns/nifcloud/)                        | [Njalla](https://go-acme.github.io/lego/dns/njalla/)                            | [NS1](https://go-acme.github.io/lego/dns/ns1/)                                  | [Open Telekom Cloud](https://go-acme.github.io/lego/dns/otc/)                   |
-| [Oracle Cloud](https://go-acme.github.io/lego/dns/oraclecloud/)                 | [OVH](https://go-acme.github.io/lego/dns/ovh/)                                  | [PowerDNS](https://go-acme.github.io/lego/dns/pdns/)                            | [Rackspace](https://go-acme.github.io/lego/dns/rackspace/)                      |
-| [reg.ru](https://go-acme.github.io/lego/dns/regru/)                             | [RFC2136](https://go-acme.github.io/lego/dns/rfc2136/)                          | [RimuHosting](https://go-acme.github.io/lego/dns/rimuhosting/)                  | [Sakura Cloud](https://go-acme.github.io/lego/dns/sakuracloud/)                 |
-| [Scaleway](https://go-acme.github.io/lego/dns/scaleway/)                        | [Selectel](https://go-acme.github.io/lego/dns/selectel/)                        | [Servercow](https://go-acme.github.io/lego/dns/servercow/)                      | [Sonic](https://go-acme.github.io/lego/dns/sonic/)                              |
-| [Stackpath](https://go-acme.github.io/lego/dns/stackpath/)                      | [TransIP](https://go-acme.github.io/lego/dns/transip/)                          | [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/)                          | [Versio.[nl/eu/uk]](https://go-acme.github.io/lego/dns/versio/)                 |
-| [VinylDNS](https://go-acme.github.io/lego/dns/vinyldns/)                        | [Vscale](https://go-acme.github.io/lego/dns/vscale/)                            | [Vultr](https://go-acme.github.io/lego/dns/vultr/)                              | [WEDOS](https://go-acme.github.io/lego/dns/wedos/)                              |
-| [Yandex](https://go-acme.github.io/lego/dns/yandex/)                            | [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/)                           | [Zonomi](https://go-acme.github.io/lego/dns/zonomi/)                            |                                                                                 |
+| [Oracle Cloud](https://go-acme.github.io/lego/dns/oraclecloud/)                 | [OVH](https://go-acme.github.io/lego/dns/ovh/)                                  | [Porkbun](https://go-acme.github.io/lego/dns/porkbun/)                          | [PowerDNS](https://go-acme.github.io/lego/dns/pdns/)                            |
+| [Rackspace](https://go-acme.github.io/lego/dns/rackspace/)                      | [reg.ru](https://go-acme.github.io/lego/dns/regru/)                             | [RFC2136](https://go-acme.github.io/lego/dns/rfc2136/)                          | [RimuHosting](https://go-acme.github.io/lego/dns/rimuhosting/)                  |
+| [Sakura Cloud](https://go-acme.github.io/lego/dns/sakuracloud/)                 | [Scaleway](https://go-acme.github.io/lego/dns/scaleway/)                        | [Selectel](https://go-acme.github.io/lego/dns/selectel/)                        | [Servercow](https://go-acme.github.io/lego/dns/servercow/)                      |
+| [Sonic](https://go-acme.github.io/lego/dns/sonic/)                              | [Stackpath](https://go-acme.github.io/lego/dns/stackpath/)                      | [TransIP](https://go-acme.github.io/lego/dns/transip/)                          | [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/)                          |
+| [Versio.[nl/eu/uk]](https://go-acme.github.io/lego/dns/versio/)                 | [VinylDNS](https://go-acme.github.io/lego/dns/vinyldns/)                        | [Vscale](https://go-acme.github.io/lego/dns/vscale/)                            | [Vultr](https://go-acme.github.io/lego/dns/vultr/)                              |
+| [WEDOS](https://go-acme.github.io/lego/dns/wedos/)                              | [Yandex](https://go-acme.github.io/lego/dns/yandex/)                            | [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/)                           | [Zonomi](https://go-acme.github.io/lego/dns/zonomi/)                            |
 
 <!-- END DNS PROVIDERS LIST -->
diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go
index 22199b0a..7e3729a1 100644
--- a/cmd/zz_gen_cmd_dnshelp.go
+++ b/cmd/zz_gen_cmd_dnshelp.go
@@ -79,6 +79,7 @@ func allDNSCodes() string {
 		"otc",
 		"ovh",
 		"pdns",
+		"porkbun",
 		"rackspace",
 		"regru",
 		"rfc2136",
@@ -1486,6 +1487,27 @@ func displayDNSHelp(name string) error {
 		ew.writeln()
 		ew.writeln(`More information: https://go-acme.github.io/lego/dns/pdns`)
 
+	case "porkbun":
+		// generated from: providers/dns/porkbun/porkbun.toml
+		ew.writeln(`Configuration for Porkbun.`)
+		ew.writeln(`Code:	'porkbun'`)
+		ew.writeln(`Since:	'v4.4.0'`)
+		ew.writeln()
+
+		ew.writeln(`Credentials:`)
+		ew.writeln(`	- "PORKBUN_API_KEY":	API key`)
+		ew.writeln(`	- "PORKBUN_SECRET_API_KEY":	secret API key`)
+		ew.writeln()
+
+		ew.writeln(`Additional Configuration:`)
+		ew.writeln(`	- "PORKBUN_HTTP_TIMEOUT":	API request timeout`)
+		ew.writeln(`	- "PORKBUN_POLLING_INTERVAL":	Time between DNS propagation check`)
+		ew.writeln(`	- "PORKBUN_PROPAGATION_TIMEOUT":	Maximum waiting time for DNS propagation`)
+		ew.writeln(`	- "PORKBUN_TTL":	The TTL of the TXT record used for the DNS challenge`)
+
+		ew.writeln()
+		ew.writeln(`More information: https://go-acme.github.io/lego/dns/porkbun`)
+
 	case "rackspace":
 		// generated from: providers/dns/rackspace/rackspace.toml
 		ew.writeln(`Configuration for Rackspace.`)
diff --git a/docs/content/dns/zz_gen_porkbun.md b/docs/content/dns/zz_gen_porkbun.md
new file mode 100644
index 00000000..64a0637e
--- /dev/null
+++ b/docs/content/dns/zz_gen_porkbun.md
@@ -0,0 +1,64 @@
+---
+title: "Porkbun"
+date: 2019-03-03T16:39:46+01:00
+draft: false
+slug: porkbun
+---
+
+<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
+<!-- providers/dns/porkbun/porkbun.toml -->
+<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
+
+Since: v4.4.0
+
+Configuration for [Porkbun](https://porkbun.com/).
+
+
+<!--more-->
+
+- Code: `porkbun`
+
+Here is an example bash command using the Porkbun provider:
+
+```bash
+PORKBUN_SECRET_API_KEY=xxxxxx \
+PORKBUN_PAPI_KEY=yyyyyy \
+lego --email myemail@example.com --dns porkbun --domains my.example.org run
+```
+
+
+
+
+## Credentials
+
+| Environment Variable Name | Description |
+|-----------------------|-------------|
+| `PORKBUN_API_KEY` | API key |
+| `PORKBUN_SECRET_API_KEY` | secret API key |
+
+The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
+More information [here](/lego/dns/#configuration-and-credentials).
+
+
+## Additional Configuration
+
+| Environment Variable Name | Description |
+|--------------------------------|-------------|
+| `PORKBUN_HTTP_TIMEOUT` | API request timeout |
+| `PORKBUN_POLLING_INTERVAL` | Time between DNS propagation check |
+| `PORKBUN_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
+| `PORKBUN_TTL` | The TTL of the TXT record used for the DNS challenge |
+
+The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
+More information [here](/lego/dns/#configuration-and-credentials).
+
+
+
+
+## More information
+
+- [API documentation](https://porkbun.com/api/json/v3/documentation)
+
+<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
+<!-- providers/dns/porkbun/porkbun.toml -->
+<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
diff --git a/go.mod b/go.mod
index 83b8d52f..c59a2dc5 100644
--- a/go.mod
+++ b/go.mod
@@ -36,6 +36,7 @@ require (
 	github.com/nrdcg/dnspod-go v0.4.0
 	github.com/nrdcg/goinwx v0.8.1
 	github.com/nrdcg/namesilo v0.2.1
+	github.com/nrdcg/porkbun v0.1.1
 	github.com/oracle/oci-go-sdk v24.3.0+incompatible
 	github.com/ovh/go-ovh v1.1.0
 	github.com/pquerna/otp v1.3.0
diff --git a/go.sum b/go.sum
index dbd3b146..291c7b1d 100644
--- a/go.sum
+++ b/go.sum
@@ -343,6 +343,8 @@ github.com/nrdcg/goinwx v0.8.1 h1:20EQ/JaGFnSKwiDH2JzjIpicffl3cPk6imJBDqVBVtU=
 github.com/nrdcg/goinwx v0.8.1/go.mod h1:tILVc10gieBp/5PMvbcYeXM6pVQ+c9jxDZnpaR1UW7c=
 github.com/nrdcg/namesilo v0.2.1 h1:kLjCjsufdW/IlC+iSfAqj0iQGgKjlbUUeDJio5Y6eMg=
 github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw=
+github.com/nrdcg/porkbun v0.1.1 h1:gxVzQYfFUGXhnBax/aVugoE3OIBAdHgrJgyMPyY5Sjo=
+github.com/nrdcg/porkbun v0.1.1/go.mod h1:JWl/WKnguWos4mjfp4YizvvToigk9qpQwrodOk+CPoA=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
diff --git a/providers/dns/checkdomain/client_test.go b/providers/dns/checkdomain/client_test.go
index 3d470241..efc75101 100644
--- a/providers/dns/checkdomain/client_test.go
+++ b/providers/dns/checkdomain/client_test.go
@@ -39,6 +39,7 @@ func Test_getDomainIDByName(t *testing.T) {
 	handler.HandleFunc("/v1/domains", func(rw http.ResponseWriter, req *http.Request) {
 		if req.Method != http.MethodGet {
 			http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest)
+			return
 		}
 
 		domainList := DomainListingResponse{
@@ -67,6 +68,7 @@ func Test_checkNameservers(t *testing.T) {
 	handler.HandleFunc("/v1/domains/1/nameservers", func(rw http.ResponseWriter, req *http.Request) {
 		if req.Method != http.MethodGet {
 			http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest)
+			return
 		}
 
 		nsResp := NameserverResponse{
@@ -94,6 +96,7 @@ func Test_createRecord(t *testing.T) {
 	handler.HandleFunc("/v1/domains/1/nameservers/records", func(rw http.ResponseWriter, req *http.Request) {
 		if req.Method != http.MethodPost {
 			http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest)
+			return
 		}
 
 		content, err := ioutil.ReadAll(req.Body)
@@ -172,6 +175,7 @@ func Test_deleteTXTRecord(t *testing.T) {
 	handler.HandleFunc("/v1/domains/1/nameservers", func(rw http.ResponseWriter, req *http.Request) {
 		if req.Method != http.MethodGet {
 			http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest)
+			return
 		}
 
 		nsResp := NameserverResponse{
diff --git a/providers/dns/dns_providers.go b/providers/dns/dns_providers.go
index bf8ba05c..fb6deb35 100644
--- a/providers/dns/dns_providers.go
+++ b/providers/dns/dns_providers.go
@@ -70,6 +70,7 @@ import (
 	"github.com/go-acme/lego/v4/providers/dns/otc"
 	"github.com/go-acme/lego/v4/providers/dns/ovh"
 	"github.com/go-acme/lego/v4/providers/dns/pdns"
+	"github.com/go-acme/lego/v4/providers/dns/porkbun"
 	"github.com/go-acme/lego/v4/providers/dns/rackspace"
 	"github.com/go-acme/lego/v4/providers/dns/regru"
 	"github.com/go-acme/lego/v4/providers/dns/rfc2136"
@@ -228,6 +229,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) {
 		return ovh.NewDNSProvider()
 	case "pdns":
 		return pdns.NewDNSProvider()
+	case "porkbun":
+		return porkbun.NewDNSProvider()
 	case "rackspace":
 		return rackspace.NewDNSProvider()
 	case "regru":
@@ -256,10 +259,10 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) {
 		return vegadns.NewDNSProvider()
 	case "versio":
 		return versio.NewDNSProvider()
-	case "vultr":
-		return vultr.NewDNSProvider()
 	case "vinyldns":
 		return vinyldns.NewDNSProvider()
+	case "vultr":
+		return vultr.NewDNSProvider()
 	case "vscale":
 		return vscale.NewDNSProvider()
 	case "wedos":
diff --git a/providers/dns/porkbun/porkbun.go b/providers/dns/porkbun/porkbun.go
new file mode 100644
index 00000000..1ec5e52a
--- /dev/null
+++ b/providers/dns/porkbun/porkbun.go
@@ -0,0 +1,181 @@
+// Package porkbun implements a DNS provider for solving the DNS-01 challenge using Porkbun.
+package porkbun
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"net/http"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+
+	"github.com/go-acme/lego/v4/challenge/dns01"
+	"github.com/go-acme/lego/v4/platform/config/env"
+	"github.com/nrdcg/porkbun"
+)
+
+// Environment variables names.
+const (
+	envNamespace = "PORKBUN_"
+
+	EnvSecretAPIKey = envNamespace + "SECRET_API_KEY"
+	EnvAPIKey       = envNamespace + "API_KEY"
+
+	EnvTTL                = envNamespace + "TTL"
+	EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
+	EnvPollingInterval    = envNamespace + "POLLING_INTERVAL"
+	EnvHTTPTimeout        = envNamespace + "HTTP_TIMEOUT"
+)
+
+const minTTL = 300
+
+// Config is used to configure the creation of the DNSProvider.
+type Config struct {
+	APIKey             string
+	SecretAPIKey       string
+	PropagationTimeout time.Duration
+	PollingInterval    time.Duration
+	TTL                int
+	HTTPClient         *http.Client
+}
+
+// NewDefaultConfig returns a default configuration for the DNSProvider.
+func NewDefaultConfig() *Config {
+	return &Config{
+		PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 10*time.Minute),
+		PollingInterval:    env.GetOrDefaultSecond(EnvPollingInterval, 10*time.Second),
+		TTL:                env.GetOrDefaultInt(EnvTTL, minTTL),
+		HTTPClient: &http.Client{
+			Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
+		},
+	}
+}
+
+// DNSProvider implements the challenge.Provider interface.
+type DNSProvider struct {
+	config *Config
+	client *porkbun.Client
+
+	recordIDs   map[string]int
+	recordIDsMu sync.Mutex
+}
+
+// NewDNSProvider returns a DNSProvider instance configured for Porkbun.
+// Credentials must be passed in the environment variables:
+// PORKBUN_SECRET_API_KEY, PORKBUN_PAPI_KEY.
+func NewDNSProvider() (*DNSProvider, error) {
+	values, err := env.Get(EnvSecretAPIKey, EnvAPIKey)
+	if err != nil {
+		return nil, fmt.Errorf("porkbun: %w", err)
+	}
+
+	config := NewDefaultConfig()
+	config.SecretAPIKey = values[EnvSecretAPIKey]
+	config.APIKey = values[EnvAPIKey]
+
+	return NewDNSProviderConfig(config)
+}
+
+// NewDNSProviderConfig return a DNSProvider instance configured for Porkbun.
+func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
+	if config == nil {
+		return nil, errors.New("porkbun: the configuration of the DNS provider is nil")
+	}
+
+	if config.SecretAPIKey == "" || config.APIKey == "" {
+		return nil, errors.New("porkbun: some credentials information are missing")
+	}
+
+	if config.TTL < minTTL {
+		return nil, fmt.Errorf("porkbun: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL)
+	}
+
+	client := porkbun.New(config.SecretAPIKey, config.APIKey)
+
+	if config.HTTPClient != nil {
+		client.HTTPClient = config.HTTPClient
+	}
+
+	return &DNSProvider{
+		config:    config,
+		client:    client,
+		recordIDs: make(map[string]int),
+	}, nil
+}
+
+// Timeout returns the timeout and interval to use when checking for DNS propagation.
+// Adjusting here to cope with spikes in propagation times.
+func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
+	return d.config.PropagationTimeout, d.config.PollingInterval
+}
+
+// Present creates a TXT record to fulfill the dns-01 challenge.
+func (d *DNSProvider) Present(domain, token, keyAuth string) error {
+	fqdn, value := dns01.GetRecord(domain, keyAuth)
+
+	zoneName, hostName, err := splitDomain(fqdn)
+	if err != nil {
+		return fmt.Errorf("porkbun: %w", err)
+	}
+
+	record := porkbun.Record{
+		Name:    hostName,
+		Type:    "TXT",
+		Content: value,
+		TTL:     strconv.Itoa(d.config.TTL),
+	}
+
+	ctx := context.Background()
+
+	recordID, err := d.client.CreateRecord(ctx, dns01.UnFqdn(zoneName), record)
+	if err != nil {
+		return fmt.Errorf("porkbun: failed to create record: %w", err)
+	}
+
+	d.recordIDsMu.Lock()
+	d.recordIDs[token] = recordID
+	d.recordIDsMu.Unlock()
+
+	return nil
+}
+
+// CleanUp removes the TXT record matching the specified parameters.
+func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
+	fqdn, _ := dns01.GetRecord(domain, keyAuth)
+
+	// gets the record's unique ID from when we created it
+	d.recordIDsMu.Lock()
+	recordID, ok := d.recordIDs[token]
+	d.recordIDsMu.Unlock()
+	if !ok {
+		return fmt.Errorf("porkbun: unknown record ID for '%s' '%s'", fqdn, token)
+	}
+
+	zoneName, _, err := splitDomain(fqdn)
+	if err != nil {
+		return fmt.Errorf("porkbun: %w", err)
+	}
+
+	ctx := context.Background()
+
+	err = d.client.DeleteRecord(ctx, dns01.UnFqdn(zoneName), recordID)
+	if err != nil {
+		return fmt.Errorf("porkbun: failed to delete record: %w", err)
+	}
+
+	return nil
+}
+
+// splitDomain splits the hostname from the authoritative zone, and returns both parts.
+func splitDomain(fqdn string) (string, string, error) {
+	zone, err := dns01.FindZoneByFqdn(fqdn)
+	if err != nil {
+		return "", "", err
+	}
+
+	host := dns01.UnFqdn(strings.TrimSuffix(fqdn, zone))
+
+	return zone, host, nil
+}
diff --git a/providers/dns/porkbun/porkbun.toml b/providers/dns/porkbun/porkbun.toml
new file mode 100644
index 00000000..ff406148
--- /dev/null
+++ b/providers/dns/porkbun/porkbun.toml
@@ -0,0 +1,24 @@
+Name = "Porkbun"
+Description = ''''''
+URL = "https://porkbun.com/"
+Code = "porkbun"
+Since = "v4.4.0"
+
+Example = '''
+PORKBUN_SECRET_API_KEY=xxxxxx \
+PORKBUN_PAPI_KEY=yyyyyy \
+lego --email myemail@example.com --dns porkbun --domains my.example.org run
+'''
+
+[Configuration]
+  [Configuration.Credentials]
+    PORKBUN_SECRET_API_KEY = "secret API key"
+    PORKBUN_API_KEY = "API key"
+  [Configuration.Additional]
+    PORKBUN_POLLING_INTERVAL = "Time between DNS propagation check"
+    PORKBUN_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
+    PORKBUN_TTL = "The TTL of the TXT record used for the DNS challenge"
+    PORKBUN_HTTP_TIMEOUT = "API request timeout"
+
+[Links]
+  API = "https://porkbun.com/api/json/v3/documentation"
diff --git a/providers/dns/porkbun/porkbun_test.go b/providers/dns/porkbun/porkbun_test.go
new file mode 100644
index 00000000..9bc86adc
--- /dev/null
+++ b/providers/dns/porkbun/porkbun_test.go
@@ -0,0 +1,150 @@
+package porkbun
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/go-acme/lego/v4/platform/tester"
+	"github.com/stretchr/testify/require"
+)
+
+const envDomain = envNamespace + "DOMAIN"
+
+var envTest = tester.NewEnvTest(EnvSecretAPIKey, EnvAPIKey).
+	WithDomain(envDomain)
+
+func TestNewDNSProvider(t *testing.T) {
+	testCases := []struct {
+		desc     string
+		envVars  map[string]string
+		expected string
+	}{
+		{
+			desc: "success",
+			envVars: map[string]string{
+				EnvSecretAPIKey: "secret",
+				EnvAPIKey:       "key",
+			},
+		},
+		{
+			desc: "missing secret API key",
+			envVars: map[string]string{
+				EnvSecretAPIKey: "",
+				EnvAPIKey:       "key",
+			},
+			expected: "porkbun: some credentials information are missing: PORKBUN_SECRET_API_KEY",
+		},
+		{
+			desc: "missing API key",
+			envVars: map[string]string{
+				EnvSecretAPIKey: "secret",
+				EnvAPIKey:       "",
+			},
+			expected: "porkbun: some credentials information are missing: PORKBUN_API_KEY",
+		},
+		{
+			desc: "missing all credentials",
+			envVars: map[string]string{
+				EnvSecretAPIKey: "",
+				EnvAPIKey:       "",
+			},
+			expected: "porkbun: some credentials information are missing: PORKBUN_SECRET_API_KEY,PORKBUN_API_KEY",
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.desc, func(t *testing.T) {
+			defer envTest.RestoreEnv()
+			envTest.ClearEnv()
+
+			envTest.Apply(test.envVars)
+
+			p, err := NewDNSProvider()
+
+			if test.expected == "" {
+				require.NoError(t, err)
+				require.NotNil(t, p)
+				require.NotNil(t, p.config)
+			} else {
+				require.EqualError(t, err, test.expected)
+			}
+		})
+	}
+}
+
+func TestNewDNSProviderConfig(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		secretAPIKey string
+		apiKey       string
+		expected     string
+	}{
+		{
+			desc:         "success",
+			secretAPIKey: "secret",
+			apiKey:       "key",
+		},
+		{
+			desc:     "missing secret API key",
+			apiKey:   "key",
+			expected: "porkbun: some credentials information are missing",
+		},
+		{
+			desc:         "missing API key",
+			secretAPIKey: "secret",
+			expected:     "porkbun: some credentials information are missing",
+		},
+		{
+			desc:     "missing all credentials",
+			expected: "porkbun: some credentials information are missing",
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.desc, func(t *testing.T) {
+			config := NewDefaultConfig()
+			config.SecretAPIKey = test.secretAPIKey
+			config.APIKey = test.apiKey
+
+			p, err := NewDNSProviderConfig(config)
+
+			if test.expected == "" {
+				require.NoError(t, err)
+				require.NotNil(t, p)
+				require.NotNil(t, p.config)
+			} else {
+				require.EqualError(t, err, test.expected)
+			}
+		})
+	}
+}
+
+func TestLivePresent(t *testing.T) {
+	if !envTest.IsLiveTest() {
+		t.Skip("skipping live test")
+	}
+
+	envTest.RestoreEnv()
+	provider, err := NewDNSProvider()
+	require.NoError(t, err)
+
+	err = provider.Present(envTest.GetDomain(), "", "123d==")
+	require.NoError(t, err)
+}
+
+func TestLiveCleanUp(t *testing.T) {
+	if !envTest.IsLiveTest() {
+		t.Skip("skipping live test")
+	}
+
+	envTest.RestoreEnv()
+	provider, err := NewDNSProvider()
+	require.NoError(t, err)
+
+	err = provider.CleanUp(envTest.GetDomain(), "", "123d==")
+	require.NoError(t, err)
+}
+
+func TestName(t *testing.T) {
+	fmt.Println(splitDomain("_acme-challenge.example.com."))
+}