Repository: go-acme/lego
Pull request #1835 (2023-02-09 07:56:51)

chore: update to go1.19
- updates CI to use go1.19 and go1.20
- uses either `URL.JoinPath` or `url.JoinPath` instead of `url.Parse`+ `path.Join` => removes more than 250 lines!
- update linter

https://tip.golang.org/doc/go1.19

Note:
`URL.JoinPath` (or `url.JoinPath`) is not the exact equivalent of `url.Parse`+ `path.Join` because the path elements in `URL.JoinPath` are path encoded which means that if an element contains a query string the query string will be path encoded (`%3F` instead of `?`).


dmke: Will take a look later.

Patch:
diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml
index 542b7d38..9a1896d6 100644
--- a/.github/workflows/documentation.yml
+++ b/.github/workflows/documentation.yml
@@ -11,7 +11,7 @@ jobs:
     name: Build and deploy documentation
     runs-on: ubuntu-latest
     env:
-      GO_VERSION: 1.19
+      GO_VERSION: '1.20'
       HUGO_VERSION: 0.101.0
       CGO_ENABLED: 0
 
diff --git a/.github/workflows/go-cross.yml b/.github/workflows/go-cross.yml
index 6e69e159..1839a0e7 100644
--- a/.github/workflows/go-cross.yml
+++ b/.github/workflows/go-cross.yml
@@ -16,7 +16,7 @@ jobs:
 
     strategy:
       matrix:
-        go-version: [ 1.18, 1.19, 1.x ]
+        go-version: [ '1.19', '1.20', 1.x ]
         os: [ubuntu-latest, macos-latest, windows-latest]
 
     steps:
diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
index 21f6d519..69cb98ac 100644
--- a/.github/workflows/pr.yml
+++ b/.github/workflows/pr.yml
@@ -12,8 +12,8 @@ jobs:
     name: Main Process
     runs-on: ubuntu-latest
     env:
-      GO_VERSION: 1.19
-      GOLANGCI_LINT_VERSION: v1.50.1
+      GO_VERSION: '1.20'
+      GOLANGCI_LINT_VERSION: v1.51.1
       HUGO_VERSION: 0.54.0
       CGO_ENABLED: 0
       LEGO_E2E_TESTS: CI
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 43ae5231..986b721d 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -11,7 +11,7 @@ jobs:
     name: Release version
     runs-on: ubuntu-latest
     env:
-      GO_VERSION: 1.19
+      GO_VERSION: '1.20'
       SEIHON_VERSION: v0.8.3
       CGO_ENABLED: 0
 
diff --git a/.golangci.toml b/.golangci.toml
index 66c3e6b6..134d894f 100644
--- a/.golangci.toml
+++ b/.golangci.toml
@@ -96,6 +96,7 @@
     "errname",
     "errchkjson",
     "nonamedreturns",
+    "musttag", # false-positive https://github.com/junk1tm/musttag/issues/17
   ]
 
 [issues]
diff --git a/go.mod b/go.mod
index efd0ec70..cb2425ee 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module github.com/go-acme/lego/v4
 
-go 1.18
+go 1.19
 
 // github.com/exoscale/egoscale v1.19.0 => It is an error, please don't use it.
 require (
@@ -10,12 +10,12 @@ require (
 	github.com/Azure/go-autorest/autorest/azure/auth v0.5.11
 	github.com/Azure/go-autorest/autorest/to v0.4.0
 	github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
-	github.com/BurntSushi/toml v1.2.0
+	github.com/BurntSushi/toml v1.2.1
 	github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87
 	github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.1
 	github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755
 	github.com/aws/aws-sdk-go v1.39.0
-	github.com/cenkalti/backoff/v4 v4.1.3
+	github.com/cenkalti/backoff/v4 v4.2.0
 	github.com/civo/civogo v0.3.11
 	github.com/cloudflare/cloudflare-go v0.49.0
 	github.com/cpu/goacmedns v0.1.1
@@ -31,7 +31,7 @@ require (
 	github.com/labbsr0x/bindman-dns-webhook v1.0.2
 	github.com/linode/linodego v1.9.1
 	github.com/liquidweb/liquidweb-go v1.6.3
-	github.com/mattn/go-isatty v0.0.16
+	github.com/mattn/go-isatty v0.0.17
 	github.com/miekg/dns v1.1.50
 	github.com/mimuret/golang-iij-dpf v0.7.1
 	github.com/mitchellh/mapstructure v1.5.0
@@ -51,7 +51,7 @@ require (
 	github.com/sacloud/iaas-api-go v1.3.2
 	github.com/scaleway/scaleway-sdk-go v1.0.0-beta.9
 	github.com/softlayer/softlayer-go v1.0.6
-	github.com/stretchr/testify v1.8.0
+	github.com/stretchr/testify v1.8.1
 	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490
 	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490
 	github.com/transip/gotransip/v6 v6.17.0
@@ -61,10 +61,10 @@ require (
 	github.com/vultr/govultr/v2 v2.17.2
 	github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f
 	github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997
-	golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29
-	golang.org/x/net v0.1.0
-	golang.org/x/oauth2 v0.1.0
-	golang.org/x/time v0.0.0-20220224211638-0e9765cccd65
+	golang.org/x/crypto v0.5.0
+	golang.org/x/net v0.5.0
+	golang.org/x/oauth2 v0.4.0
+	golang.org/x/time v0.3.0
 	google.golang.org/api v0.20.0
 	gopkg.in/ns1/ns1-go.v2 v2.6.5
 	gopkg.in/yaml.v2 v2.4.0
@@ -118,13 +118,13 @@ require (
 	github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
 	github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
 	github.com/spf13/cast v1.3.1 // indirect
-	github.com/stretchr/objx v0.4.0 // indirect
+	github.com/stretchr/objx v0.5.0 // indirect
 	github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
 	go.opencensus.io v0.22.3 // indirect
 	go.uber.org/ratelimit v0.2.0 // indirect
 	golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
-	golang.org/x/sys v0.1.0 // indirect
-	golang.org/x/text v0.4.0 // indirect
+	golang.org/x/sys v0.4.0 // indirect
+	golang.org/x/text v0.6.0 // indirect
 	golang.org/x/tools v0.1.12 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
 	google.golang.org/genproto v0.0.0-20211021150943-2b146023228c // indirect
diff --git a/go.sum b/go.sum
index 2c0e9440..293d7475 100644
--- a/go.sum
+++ b/go.sum
@@ -48,8 +48,8 @@ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ
 github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
 github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0=
-github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
+github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 h1:xPMsUicZ3iosVPSIP7bW5EcGUzjiiMl1OYTe14y/R24=
@@ -77,8 +77,8 @@ github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8
 github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
 github.com/c-bata/go-prompt v0.2.5/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7FVlAwDAVw=
 github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
-github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4=
-github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
+github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4=
+github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -367,8 +367,8 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
 github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
-github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
-github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
+github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0=
@@ -526,8 +526,9 @@ github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5q
 github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@@ -535,8 +536,9 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 h1:mmz27tVi2r70JYnm5y0Zk8w0Qzsx+vfUw3oqSyrEfP8=
 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
@@ -603,8 +605,9 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o=
 golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
+golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -674,15 +677,15 @@ golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qx
 golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
-golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
+golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
+golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.1.0 h1:isLCZuhj4v+tYv7eskaN4v/TM+A1begWWgyVJDdl1+Y=
-golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A=
+golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M=
+golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -745,12 +748,12 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
-golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
+golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
+golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -760,15 +763,15 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
-golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
+golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs=
-golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
+golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/providers/dns/arvancloud/internal/client.go b/providers/dns/arvancloud/internal/client.go
index cbfdf164..9cf5b85a 100644
--- a/providers/dns/arvancloud/internal/client.go
+++ b/providers/dns/arvancloud/internal/client.go
@@ -7,7 +7,6 @@ import (
 	"io"
 	"net/http"
 	"net/url"
-	"path"
 	"strings"
 )
 
@@ -178,12 +177,7 @@ func (c *Client) createEndpoint(parts ...string) (*url.URL, error) {
 		return nil, err
 	}
 
-	endpoint, err := baseURL.Parse(path.Join(parts...))
-	if err != nil {
-		return nil, err
-	}
-
-	return endpoint, nil
+	return baseURL.JoinPath(parts...), nil
 }
 
 func equalsTXTRecord(record DNSRecord, name, value string) bool {
diff --git a/providers/dns/autodns/client.go b/providers/dns/autodns/client.go
index 1c2306c8..1c58ed81 100644
--- a/providers/dns/autodns/client.go
+++ b/providers/dns/autodns/client.go
@@ -6,7 +6,6 @@ import (
 	"fmt"
 	"io"
 	"net/http"
-	"path"
 	"strconv"
 )
 
@@ -85,7 +84,9 @@ func (d *DNSProvider) makeZoneUpdateRequest(zoneStream *ZoneStream, domain strin
 		return nil, err
 	}
 
-	req, err := d.makeRequest(http.MethodPost, path.Join("zone", domain, "_stream"), reqBody)
+	endpoint := d.config.Endpoint.JoinPath("zone", domain, "_stream")
+
+	req, err := d.makeRequest(http.MethodPost, endpoint.String(), reqBody)
 	if err != nil {
 		return nil, err
 	}
@@ -97,13 +98,8 @@ func (d *DNSProvider) makeZoneUpdateRequest(zoneStream *ZoneStream, domain strin
 	return resp, nil
 }
 
-func (d *DNSProvider) makeRequest(method, resource string, body io.Reader) (*http.Request, error) {
-	uri, err := d.config.Endpoint.Parse(resource)
-	if err != nil {
-		return nil, err
-	}
-
-	req, err := http.NewRequest(method, uri.String(), body)
+func (d *DNSProvider) makeRequest(method, endpoint string, body io.Reader) (*http.Request, error) {
+	req, err := http.NewRequest(method, endpoint, body)
 	if err != nil {
 		return nil, err
 	}
diff --git a/providers/dns/cloudns/internal/client.go b/providers/dns/cloudns/internal/client.go
index a63ecc84..65270f80 100644
--- a/providers/dns/cloudns/internal/client.go
+++ b/providers/dns/cloudns/internal/client.go
@@ -7,7 +7,6 @@ import (
 	"io"
 	"net/http"
 	"net/url"
-	"path"
 	"strconv"
 
 	"github.com/go-acme/lego/v4/challenge/dns01"
@@ -57,10 +56,7 @@ func (c *Client) GetZone(authFQDN string) (*Zone, error) {
 
 	authZoneName := dns01.UnFqdn(authZone)
 
-	endpoint, err := c.BaseURL.Parse(path.Join(c.BaseURL.Path, "get-zone-info.json"))
-	if err != nil {
-		return nil, fmt.Errorf("failed to parse endpoint: %w", err)
-	}
+	endpoint := c.BaseURL.JoinPath("get-zone-info.json")
 
 	q := endpoint.Query()
 	q.Set("domain-name", authZoneName)
@@ -93,18 +89,15 @@ func (c *Client) FindTxtRecord(zoneName, fqdn string) (*TXTRecord, error) {
 		return nil, err
 	}
 
-	reqURL, err := c.BaseURL.Parse(path.Join(c.BaseURL.Path, "records.json"))
-	if err != nil {
-		return nil, fmt.Errorf("failed to parse endpoint: %w", err)
-	}
+	endpoint := c.BaseURL.JoinPath("records.json")
 
-	q := reqURL.Query()
+	q := endpoint.Query()
 	q.Set("domain-name", zoneName)
 	q.Set("host", subDomain)
 	q.Set("type", "TXT")
-	reqURL.RawQuery = q.Encode()
+	endpoint.RawQuery = q.Encode()
 
-	result, err := c.doRequest(http.MethodGet, reqURL)
+	result, err := c.doRequest(http.MethodGet, endpoint)
 	if err != nil {
 		return nil, err
 	}
@@ -135,18 +128,15 @@ func (c *Client) ListTxtRecords(zoneName, fqdn string) ([]TXTRecord, error) {
 		return nil, err
 	}
 
-	reqURL, err := c.BaseURL.Parse(path.Join(c.BaseURL.Path, "records.json"))
-	if err != nil {
-		return nil, fmt.Errorf("failed to parse endpoint: %w", err)
-	}
+	endpoint := c.BaseURL.JoinPath("records.json")
 
-	q := reqURL.Query()
+	q := endpoint.Query()
 	q.Set("domain-name", zoneName)
 	q.Set("host", subDomain)
 	q.Set("type", "TXT")
-	reqURL.RawQuery = q.Encode()
+	endpoint.RawQuery = q.Encode()
 
-	result, err := c.doRequest(http.MethodGet, reqURL)
+	result, err := c.doRequest(http.MethodGet, endpoint)
 	if err != nil {
 		return nil, err
 	}
@@ -178,20 +168,17 @@ func (c *Client) AddTxtRecord(zoneName, fqdn, value string, ttl int) error {
 		return err
 	}
 
-	reqURL, err := c.BaseURL.Parse(path.Join(c.BaseURL.Path, "add-record.json"))
-	if err != nil {
-		return fmt.Errorf("failed to parse endpoint: %w", err)
-	}
+	endpoint := c.BaseURL.JoinPath("add-record.json")
 
-	q := reqURL.Query()
+	q := endpoint.Query()
 	q.Set("domain-name", zoneName)
 	q.Set("host", subDomain)
 	q.Set("record", value)
 	q.Set("ttl", strconv.Itoa(ttlRounder(ttl)))
 	q.Set("record-type", "TXT")
-	reqURL.RawQuery = q.Encode()
+	endpoint.RawQuery = q.Encode()
 
-	raw, err := c.doRequest(http.MethodPost, reqURL)
+	raw, err := c.doRequest(http.MethodPost, endpoint)
 	if err != nil {
 		return err
 	}
@@ -210,17 +197,14 @@ func (c *Client) AddTxtRecord(zoneName, fqdn, value string, ttl int) error {
 
 // RemoveTxtRecord removes a TXT record.
 func (c *Client) RemoveTxtRecord(recordID int, zoneName string) error {
-	reqURL, err := c.BaseURL.Parse(path.Join(c.BaseURL.Path, "delete-record.json"))
-	if err != nil {
-		return fmt.Errorf("failed to parse endpoint: %w", err)
-	}
+	endpoint := c.BaseURL.JoinPath("delete-record.json")
 
-	q := reqURL.Query()
+	q := endpoint.Query()
 	q.Set("domain-name", zoneName)
 	q.Set("record-id", strconv.Itoa(recordID))
-	reqURL.RawQuery = q.Encode()
+	endpoint.RawQuery = q.Encode()
 
-	raw, err := c.doRequest(http.MethodPost, reqURL)
+	raw, err := c.doRequest(http.MethodPost, endpoint)
 	if err != nil {
 		return err
 	}
@@ -239,16 +223,13 @@ func (c *Client) RemoveTxtRecord(recordID int, zoneName string) error {
 
 // GetUpdateStatus gets sync progress of all CloudDNS NS servers.
 func (c *Client) GetUpdateStatus(zoneName string) (*SyncProgress, error) {
-	reqURL, err := c.BaseURL.Parse(path.Join(c.BaseURL.Path, "update-status.json"))
-	if err != nil {
-		return nil, fmt.Errorf("failed to parse endpoint: %w", err)
-	}
+	endpoint := c.BaseURL.JoinPath("update-status.json")
 
-	q := reqURL.Query()
+	q := endpoint.Query()
 	q.Set("domain-name", zoneName)
-	reqURL.RawQuery = q.Encode()
+	endpoint.RawQuery = q.Encode()
 
-	result, err := c.doRequest(http.MethodGet, reqURL)
+	result, err := c.doRequest(http.MethodGet, endpoint)
 	if err != nil {
 		return nil, err
 	}
diff --git a/providers/dns/constellix/internal/client.go b/providers/dns/constellix/internal/client.go
index 4d4951ac..af399fa3 100644
--- a/providers/dns/constellix/internal/client.go
+++ b/providers/dns/constellix/internal/client.go
@@ -6,7 +6,6 @@ import (
 	"io"
 	"net/http"
 	"net/url"
-	"path"
 )
 
 const (
@@ -76,17 +75,7 @@ func (c *Client) do(req *http.Request, v interface{}) error {
 }
 
 func (c *Client) createEndpoint(fragment ...string) (string, error) {
-	baseURL, err := url.Parse(c.BaseURL)
-	if err != nil {
-		return "", err
-	}
-
-	endpoint, err := baseURL.Parse(path.Join(fragment...))
-	if err != nil {
-		return "", err
-	}
-
-	return endpoint.String(), nil
+	return url.JoinPath(c.BaseURL, fragment...)
 }
 
 func checkResponse(resp *http.Response) error {
diff --git a/providers/dns/dynu/internal/client.go b/providers/dns/dynu/internal/client.go
index 93ee9f15..a65681ca 100644
--- a/providers/dns/dynu/internal/client.go
+++ b/providers/dns/dynu/internal/client.go
@@ -8,7 +8,6 @@ import (
 	"io"
 	"net/http"
 	"net/url"
-	"path"
 	"strconv"
 	"time"
 
@@ -177,5 +176,5 @@ func (c Client) createEndpoint(fragments ...string) (*url.URL, error) {
 		return nil, err
 	}
 
-	return baseURL.Parse(path.Join(baseURL.Path, path.Join(fragments...)))
+	return baseURL.JoinPath(fragments...), nil
 }
diff --git a/providers/dns/easydns/client.go b/providers/dns/easydns/client.go
index 49922e33..2d3950ef 100644
--- a/providers/dns/easydns/client.go
+++ b/providers/dns/easydns/client.go
@@ -6,7 +6,7 @@ import (
 	"fmt"
 	"io"
 	"net/http"
-	"path"
+	"net/url"
 )
 
 const defaultEndpoint = "https://rest.easydns.net"
@@ -32,10 +32,10 @@ type addRecordResponse struct {
 }
 
 func (d *DNSProvider) addRecord(domain string, record interface{}) (string, error) {
-	pathAdd := path.Join("/zones/records/add", domain, "TXT")
+	endpoint := d.config.Endpoint.JoinPath("zones", "records", "add", domain, "TXT")
 
 	response := &addRecordResponse{}
-	err := d.doRequest(http.MethodPut, pathAdd, record, response)
+	err := d.doRequest(http.MethodPut, endpoint, record, response)
 	if err != nil {
 		return "", err
 	}
@@ -46,12 +46,12 @@ func (d *DNSProvider) addRecord(domain string, record interface{}) (string, erro
 }
 
 func (d *DNSProvider) deleteRecord(domain, recordID string) error {
-	pathDelete := path.Join("/zones/records", domain, recordID)
+	endpoint := d.config.Endpoint.JoinPath("zones", "records", domain, recordID)
 
-	return d.doRequest(http.MethodDelete, pathDelete, nil, nil)
+	return d.doRequest(http.MethodDelete, endpoint, nil, nil)
 }
 
-func (d *DNSProvider) doRequest(method, resource string, requestMsg, responseMsg interface{}) error {
+func (d *DNSProvider) doRequest(method string, endpoint *url.URL, requestMsg, responseMsg interface{}) error {
 	reqBody := &bytes.Buffer{}
 	if requestMsg != nil {
 		err := json.NewEncoder(reqBody).Encode(requestMsg)
@@ -60,10 +60,9 @@ func (d *DNSProvider) doRequest(method, resource string, requestMsg, responseMsg
 		}
 	}
 
-	endpoint, err := d.config.Endpoint.Parse(resource + "?format=json")
-	if err != nil {
-		return err
-	}
+	query := endpoint.Query()
+	query.Set("format", "json")
+	endpoint.RawQuery = query.Encode()
 
 	request, err := http.NewRequest(method, endpoint.String(), reqBody)
 	if err != nil {
diff --git a/providers/dns/epik/internal/client.go b/providers/dns/epik/internal/client.go
index 0ff4735e..d5fb7829 100644
--- a/providers/dns/epik/internal/client.go
+++ b/providers/dns/epik/internal/client.go
@@ -7,7 +7,6 @@ import (
 	"io"
 	"net/http"
 	"net/url"
-	"path"
 	"time"
 )
 
@@ -127,10 +126,7 @@ func (c Client) RemoveHostRecord(domain string, recordID string) (*Data, error)
 }
 
 func (c *Client) do(method, domain string, params url.Values, body io.Reader) (*http.Response, error) {
-	endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, "domains", domain, "records"))
-	if err != nil {
-		return nil, fmt.Errorf("create endpoint: %w", err)
-	}
+	endpoint := c.baseURL.JoinPath("domains", domain, "records")
 
 	params.Set("SIGNATURE", c.signature)
 	endpoint.RawQuery = params.Encode()
diff --git a/providers/dns/gcore/internal/client.go b/providers/dns/gcore/internal/client.go
index 0435ca15..18160681 100644
--- a/providers/dns/gcore/internal/client.go
+++ b/providers/dns/gcore/internal/client.go
@@ -8,7 +8,6 @@ import (
 	"io"
 	"net/http"
 	"net/url"
-	"path"
 	"strings"
 	"time"
 )
@@ -40,10 +39,10 @@ func NewClient(token string) *Client {
 // GetZone gets zone information.
 // https://dnsapi.gcorelabs.com/docs#operation/Zone
 func (c *Client) GetZone(ctx context.Context, name string) (Zone, error) {
-	zone := Zone{}
-	uri := path.Join("/v2/zones", name)
+	endpoint := c.baseURL.JoinPath("v2", "zones", name)
 
-	err := c.do(ctx, http.MethodGet, uri, nil, &zone)
+	zone := Zone{}
+	err := c.do(ctx, http.MethodGet, endpoint, nil, &zone)
 	if err != nil {
 		return Zone{}, fmt.Errorf("get zone %s: %w", name, err)
 	}
@@ -54,10 +53,10 @@ func (c *Client) GetZone(ctx context.Context, name string) (Zone, error) {
 // GetRRSet gets RRSet item.
 // https://dnsapi.gcorelabs.com/docs#operation/RRSet
 func (c *Client) GetRRSet(ctx context.Context, zone, name string) (RRSet, error) {
-	var result RRSet
-	uri := path.Join("/v2/zones", zone, name, txtRecordType)
+	endpoint := c.baseURL.JoinPath("v2", "zones", zone, name, txtRecordType)
 
-	err := c.do(ctx, http.MethodGet, uri, nil, &result)
+	var result RRSet
+	err := c.do(ctx, http.MethodGet, endpoint, nil, &result)
 	if err != nil {
 		return RRSet{}, fmt.Errorf("get txt records %s -> %s: %w", zone, name, err)
 	}
@@ -68,9 +67,9 @@ func (c *Client) GetRRSet(ctx context.Context, zone, name string) (RRSet, error)
 // DeleteRRSet removes RRSet record.
 // https://dnsapi.gcorelabs.com/docs#operation/DeleteRRSet
 func (c *Client) DeleteRRSet(ctx context.Context, zone, name string) error {
-	uri := path.Join("/v2/zones", zone, name, txtRecordType)
+	endpoint := c.baseURL.JoinPath("v2", "zones", zone, name, txtRecordType)
 
-	err := c.do(ctx, http.MethodDelete, uri, nil, nil)
+	err := c.do(ctx, http.MethodDelete, endpoint, nil, nil)
 	if err != nil {
 		// Support DELETE idempotence https://developer.mozilla.org/en-US/docs/Glossary/Idempotent
 		statusErr := new(APIError)
@@ -99,19 +98,19 @@ func (c *Client) AddRRSet(ctx context.Context, zone, recordName, value string, t
 
 // https://dnsapi.gcorelabs.com/docs#operation/CreateRRSet
 func (c *Client) createRRSet(ctx context.Context, zone, name string, record RRSet) error {
-	uri := path.Join("/v2/zones", zone, name, txtRecordType)
+	endpoint := c.baseURL.JoinPath("v2", "zones", zone, name, txtRecordType)
 
-	return c.do(ctx, http.MethodPost, uri, record, nil)
+	return c.do(ctx, http.MethodPost, endpoint, record, nil)
 }
 
 // https://dnsapi.gcorelabs.com/docs#operation/UpdateRRSet
 func (c *Client) updateRRSet(ctx context.Context, zone, name string, record RRSet) error {
-	uri := path.Join("/v2/zones", zone, name, txtRecordType)
+	endpoint := c.baseURL.JoinPath("v2", "zones", zone, name, txtRecordType)
 
-	return c.do(ctx, http.MethodPut, uri, record, nil)
+	return c.do(ctx, http.MethodPut, endpoint, record, nil)
 }
 
-func (c *Client) do(ctx context.Context, method, uri string, bodyParams interface{}, dest interface{}) error {
+func (c *Client) do(ctx context.Context, method string, endpoint *url.URL, bodyParams interface{}, dest interface{}) error {
 	var bs []byte
 	if bodyParams != nil {
 		var err error
@@ -121,11 +120,6 @@ func (c *Client) do(ctx context.Context, method, uri string, bodyParams interfac
 		}
 	}
 
-	endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, uri))
-	if err != nil {
-		return fmt.Errorf("failed to parse endpoint: %w", err)
-	}
-
 	req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), strings.NewReader(string(bs)))
 	if err != nil {
 		return fmt.Errorf("new request: %w", err)
diff --git a/providers/dns/godaddy/internal/client.go b/providers/dns/godaddy/internal/client.go
index c1096ce5..90f1ef01 100644
--- a/providers/dns/godaddy/internal/client.go
+++ b/providers/dns/godaddy/internal/client.go
@@ -82,10 +82,7 @@ func (d *Client) UpdateTxtRecords(records []DNSRecord, domainZone, recordName st
 }
 
 func (d *Client) makeRequest(method, uri string, body io.Reader) (*http.Response, error) {
-	endpoint, err := d.baseURL.Parse(path.Join(d.baseURL.Path, uri))
-	if err != nil {
-		return nil, err
-	}
+	endpoint := d.baseURL.JoinPath(uri)
 
 	req, err := http.NewRequest(method, endpoint.String(), body)
 	if err != nil {
diff --git a/providers/dns/hetzner/internal/client.go b/providers/dns/hetzner/internal/client.go
index efe8f48f..326ecdb0 100644
--- a/providers/dns/hetzner/internal/client.go
+++ b/providers/dns/hetzner/internal/client.go
@@ -7,7 +7,6 @@ import (
 	"io"
 	"net/http"
 	"net/url"
-	"path"
 )
 
 // defaultBaseURL represents the API endpoint to call.
@@ -196,10 +195,5 @@ func (c *Client) createEndpoint(parts ...string) (*url.URL, error) {
 		return nil, err
 	}
 
-	endpoint, err := baseURL.Parse(path.Join(parts...))
-	if err != nil {
-		return nil, err
-	}
-
-	return endpoint, nil
+	return baseURL.JoinPath(parts...), nil
 }
diff --git a/providers/dns/hosttech/internal/client.go b/providers/dns/hosttech/internal/client.go
index 8108438a..0786674c 100644
--- a/providers/dns/hosttech/internal/client.go
+++ b/providers/dns/hosttech/internal/client.go
@@ -7,7 +7,6 @@ import (
 	"io"
 	"net/http"
 	"net/url"
-	"path"
 	"strconv"
 	"time"
 )
@@ -36,10 +35,7 @@ func NewClient(apiKey string) *Client {
 // GetZones Get a list of all zones.
 // https://api.ns1.hosttech.eu/api/documentation/#/Zones/get_api_user_v1_zones
 func (c Client) GetZones(query string, limit, offset int) ([]Zone, error) {
-	endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, "user", "v1", "zones"))
-	if err != nil {
-		return nil, fmt.Errorf("parse URL: %w", err)
-	}
+	endpoint := c.baseURL.JoinPath("user", "v1", "zones")
 
 	values := endpoint.Query()
 	values.Set("query", query)
@@ -76,10 +72,7 @@ func (c Client) GetZones(query string, limit, offset int) ([]Zone, error) {
 // GetZone Get a single zone.
 // https://api.ns1.hosttech.eu/api/documentation/#/Zones/get_api_user_v1_zones__zoneId_
 func (c Client) GetZone(zoneID string) (*Zone, error) {
-	endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, "user", "v1", "zones", zoneID))
-	if err != nil {
-		return nil, fmt.Errorf("parse URL: %w", err)
-	}
+	endpoint := c.baseURL.JoinPath("user", "v1", "zones", zoneID)
 
 	req, err := http.NewRequest(http.MethodGet, endpoint.String(), nil)
 	if err != nil {
@@ -103,10 +96,7 @@ func (c Client) GetZone(zoneID string) (*Zone, error) {
 // GetRecords Returns a list of all records for the given zone.
 // https://api.ns1.hosttech.eu/api/documentation/#/Records/get_api_user_v1_zones__zoneId__records
 func (c Client) GetRecords(zoneID, recordType string) ([]Record, error) {
-	endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, "user", "v1", "zones", zoneID, "records"))
-	if err != nil {
-		return nil, fmt.Errorf("parse URL: %w", err)
-	}
+	endpoint := c.baseURL.JoinPath("user", "v1", "zones", zoneID, "records")
 
 	values := endpoint.Query()
 
@@ -138,10 +128,7 @@ func (c Client) GetRecords(zoneID, recordType string) ([]Record, error) {
 // AddRecord Adds a new record to the zone and returns the newly created record.
 // https://api.ns1.hosttech.eu/api/documentation/#/Records/post_api_user_v1_zones__zoneId__records
 func (c Client) AddRecord(zoneID string, record Record) (*Record, error) {
-	endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, "user", "v1", "zones", zoneID, "records"))
-	if err != nil {
-		return nil, fmt.Errorf("parse URL: %w", err)
-	}
+	endpoint := c.baseURL.JoinPath("user", "v1", "zones", zoneID, "records")
 
 	body, err := json.Marshal(record)
 	if err != nil {
@@ -170,10 +157,7 @@ func (c Client) AddRecord(zoneID string, record Record) (*Record, error) {
 // DeleteRecord Deletes a single record for the given id.
 // https://api.ns1.hosttech.eu/api/documentation/#/Records/delete_api_user_v1_zones__zoneId__records__recordId_
 func (c Client) DeleteRecord(zoneID, recordID string) error {
-	endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, "user", "v1", "zones", zoneID, "records", recordID))
-	if err != nil {
-		return fmt.Errorf("parse URL: %w", err)
-	}
+	endpoint := c.baseURL.JoinPath("user", "v1", "zones", zoneID, "records", recordID)
 
 	req, err := http.NewRequest(http.MethodDelete, endpoint.String(), nil)
 	if err != nil {
diff --git a/providers/dns/httpreq/httpreq.go b/providers/dns/httpreq/httpreq.go
index 55f2725d..d2676ec4 100644
--- a/providers/dns/httpreq/httpreq.go
+++ b/providers/dns/httpreq/httpreq.go
@@ -9,7 +9,6 @@ import (
 	"io"
 	"net/http"
 	"net/url"
-	"path"
 	"time"
 
 	"github.com/go-acme/lego/v4/challenge/dns01"
@@ -172,11 +171,7 @@ func (d *DNSProvider) doPost(uri string, msg interface{}) error {
 		return err
 	}
 
-	newURI := path.Join(d.config.Endpoint.EscapedPath(), uri)
-	endpoint, err := d.config.Endpoint.Parse(newURI)
-	if err != nil {
-		return err
-	}
+	endpoint := d.config.Endpoint.JoinPath(uri)
 
 	req, err := http.NewRequest(http.MethodPost, endpoint.String(), reqBody)
 	if err != nil {
diff --git a/providers/dns/hyperone/internal/client.go b/providers/dns/hyperone/internal/client.go
index 125315c4..1231d9bc 100644
--- a/providers/dns/hyperone/internal/client.go
+++ b/providers/dns/hyperone/internal/client.go
@@ -8,7 +8,6 @@ import (
 	"io"
 	"net/http"
 	"net/url"
-	"path"
 	"time"
 )
 
@@ -24,9 +23,7 @@ type signer interface {
 type Client struct {
 	HTTPClient *http.Client
 
-	apiEndpoint string
-	locationID  string
-	projectID   string
+	apiEndpoint *url.URL
 
 	passport *Passport
 	signer   signer
@@ -43,32 +40,34 @@ func NewClient(apiEndpoint, locationID string, passport *Passport) (*Client, err
 		return nil, err
 	}
 
-	baseURL := defaultBaseURL
-	if apiEndpoint != "" {
-		baseURL = apiEndpoint
+	if apiEndpoint == "" {
+		apiEndpoint = defaultBaseURL
+	}
+
+	baseURL, err := url.Parse(apiEndpoint)
+	if err != nil {
+		return nil, err
 	}
 
 	tokenSigner := &TokenSigner{
 		PrivateKey: passport.PrivateKey,
 		KeyID:      passport.CertificateID,
-		Audience:   baseURL,
+		Audience:   apiEndpoint,
 		Issuer:     passport.Issuer,
 		Subject:    passport.SubjectID,
 	}
 
+	if locationID == "" {
+		locationID = defaultLocationID
+	}
+
 	client := &Client{
 		HTTPClient:  &http.Client{Timeout: 5 * time.Second},
-		apiEndpoint: baseURL,
-		locationID:  locationID,
+		apiEndpoint: baseURL.JoinPath("dns", locationID, "project", projectID),
 		passport:    passport,
-		projectID:   projectID,
 		signer:      tokenSigner,
 	}
 
-	if client.locationID == "" {
-		client.locationID = defaultLocationID
-	}
-
 	return client, nil
 }
 
@@ -77,9 +76,9 @@ func NewClient(apiEndpoint, locationID string, passport *Passport) (*Client, err
 // https://api.hyperone.com/v2/docs#operation/dns_project_zone_recordset_list
 func (c *Client) FindRecordset(zoneID, recordType, name string) (*Recordset, error) {
 	// https://api.hyperone.com/v2/dns/{locationId}/project/{projectId}/zone/{zoneId}/recordset
-	resourceURL := path.Join("dns", c.locationID, "project", c.projectID, "zone", zoneID, "recordset")
+	endpoint := c.apiEndpoint.JoinPath("zone", zoneID, "recordset")
 
-	req, err := c.createRequest(http.MethodGet, resourceURL, nil)
+	req, err := c.createRequest(http.MethodGet, endpoint.String(), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -117,9 +116,9 @@ func (c *Client) CreateRecordset(zoneID, recordType, name, recordValue string, t
 	}
 
 	// https://api.hyperone.com/v2/dns/{locationId}/project/{projectId}/zone/{zoneId}/recordset
-	resourceURL := path.Join("dns", c.locationID, "project", c.projectID, "zone", zoneID, "recordset")
+	endpoint := c.apiEndpoint.JoinPath("zone", zoneID, "recordset")
 
-	req, err := c.createRequest(http.MethodPost, resourceURL, bytes.NewBuffer(requestBody))
+	req, err := c.createRequest(http.MethodPost, endpoint.String(), bytes.NewBuffer(requestBody))
 	if err != nil {
 		return nil, err
 	}
@@ -138,9 +137,9 @@ func (c *Client) CreateRecordset(zoneID, recordType, name, recordValue string, t
 // https://api.hyperone.com/v2/docs#operation/dns_project_zone_recordset_delete
 func (c *Client) DeleteRecordset(zoneID string, recordsetID string) error {
 	// https://api.hyperone.com/v2/dns/{locationId}/project/{projectId}/zone/{zoneId}/recordset/{recordsetId}
-	resourceURL := path.Join("dns", c.locationID, "project", c.projectID, "zone", zoneID, "recordset", recordsetID)
+	endpoint := c.apiEndpoint.JoinPath("zone", zoneID, "recordset", recordsetID)
 
-	req, err := c.createRequest(http.MethodDelete, resourceURL, nil)
+	req, err := c.createRequest(http.MethodDelete, endpoint.String(), nil)
 	if err != nil {
 		return err
 	}
@@ -152,9 +151,9 @@ func (c *Client) DeleteRecordset(zoneID string, recordsetID string) error {
 // https://api.hyperone.com/v2/docs#operation/dns_project_zone_recordset_record_list
 func (c *Client) GetRecords(zoneID string, recordsetID string) ([]Record, error) {
 	// https://api.hyperone.com/v2/dns/{locationId}/project/{projectId}/zone/{zoneId}/recordset/{recordsetId}/record
-	resourceURL := path.Join("dns", c.locationID, "project", c.projectID, "zone", zoneID, "recordset", recordsetID, "record")
+	endpoint := c.apiEndpoint.JoinPath("zone", zoneID, "recordset", recordsetID, "record")
 
-	req, err := c.createRequest(http.MethodGet, resourceURL, nil)
+	req, err := c.createRequest(http.MethodGet, endpoint.String(), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -173,14 +172,14 @@ func (c *Client) GetRecords(zoneID string, recordsetID string) ([]Record, error)
 // https://api.hyperone.com/v2/docs#operation/dns_project_zone_recordset_record_create
 func (c *Client) CreateRecord(zoneID, recordsetID, recordContent string) (*Record, error) {
 	// https://api.hyperone.com/v2/dns/{locationId}/project/{projectId}/zone/{zoneId}/recordset/{recordsetId}/record
-	resourceURL := path.Join("dns", c.locationID, "project", c.projectID, "zone", zoneID, "recordset", recordsetID, "record")
+	endpoint := c.apiEndpoint.JoinPath("zone", zoneID, "recordset", recordsetID, "record")
 
 	requestBody, err := json.Marshal(Record{Content: recordContent})
 	if err != nil {
 		return nil, fmt.Errorf("failed to marshal record: %w", err)
 	}
 
-	req, err := c.createRequest(http.MethodPost, resourceURL, bytes.NewBuffer(requestBody))
+	req, err := c.createRequest(http.MethodPost, endpoint.String(), bytes.NewBuffer(requestBody))
 	if err != nil {
 		return nil, err
 	}
@@ -199,9 +198,9 @@ func (c *Client) CreateRecord(zoneID, recordsetID, recordContent string) (*Recor
 // https://api.hyperone.com/v2/docs#operation/dns_project_zone_recordset_record_delete
 func (c *Client) DeleteRecord(zoneID, recordsetID, recordID string) error {
 	// https://api.hyperone.com/v2/dns/{locationId}/project/{projectId}/zone/{zoneId}/recordset/{recordsetId}/record/{recordId}
-	resourceURL := path.Join("dns", c.locationID, "project", c.projectID, "zone", zoneID, "recordset", recordsetID, "record", recordID)
+	endpoint := c.apiEndpoint.JoinPath("zone", zoneID, "recordset", recordsetID, "record", recordID)
 
-	req, err := c.createRequest(http.MethodDelete, resourceURL, nil)
+	req, err := c.createRequest(http.MethodDelete, endpoint.String(), nil)
 	if err != nil {
 		return err
 	}
@@ -229,9 +228,9 @@ func (c *Client) FindZone(name string) (*Zone, error) {
 // https://api.hyperone.com/v2/docs#operation/dns_project_zone_list
 func (c *Client) GetZones() ([]Zone, error) {
 	// https://api.hyperone.com/v2/dns/{locationId}/project/{projectId}/zone
-	resourceURL := path.Join("dns", c.locationID, "project", c.projectID, "zone")
+	endpoint := c.apiEndpoint.JoinPath("zone")
 
-	req, err := c.createRequest(http.MethodGet, resourceURL, nil)
+	req, err := c.createRequest(http.MethodGet, endpoint.String(), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -246,18 +245,8 @@ func (c *Client) GetZones() ([]Zone, error) {
 	return zones, nil
 }
 
-func (c *Client) createRequest(method, uri string, body io.Reader) (*http.Request, error) {
-	baseURL, err := url.Parse(c.apiEndpoint)
-	if err != nil {
-		return nil, err
-	}
-
-	endpoint, err := baseURL.Parse(path.Join(baseURL.Path, uri))
-	if err != nil {
-		return nil, err
-	}
-
-	req, err := http.NewRequest(method, endpoint.String(), body)
+func (c *Client) createRequest(method, endpoint string, body io.Reader) (*http.Request, error) {
+	req, err := http.NewRequest(method, endpoint, body)
 	if err != nil {
 		return nil, err
 	}
diff --git a/providers/dns/infomaniak/internal/client.go b/providers/dns/infomaniak/internal/client.go
index bd55f9b9..cb27cfb6 100644
--- a/providers/dns/infomaniak/internal/client.go
+++ b/providers/dns/infomaniak/internal/client.go
@@ -7,7 +7,7 @@ import (
 	"io"
 	"net/http"
 	"net/url"
-	"path"
+	"strconv"
 	"strings"
 	"time"
 
@@ -37,13 +37,16 @@ func (c *Client) CreateDNSRecord(domain *DNSDomain, record Record) (string, erro
 		return "", err
 	}
 
-	uri := fmt.Sprintf("/1/domain/%d/dns/record", domain.ID)
-
-	req, err := c.newRequest(http.MethodPost, uri, bytes.NewBuffer(rawJSON))
+	endpoint, err := url.JoinPath(c.apiEndpoint, "1", "domain", strconv.FormatUint(domain.ID, 10), "dns", "record")
 	if err != nil {
 		return "", err
 	}
 
+	req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(rawJSON))
+	if err != nil {
+		return "", fmt.Errorf("failed to create request: %w", err)
+	}
+
 	resp, err := c.do(req)
 	if err != nil {
 		return "", err
@@ -58,13 +61,16 @@ func (c *Client) CreateDNSRecord(domain *DNSDomain, record Record) (string, erro
 }
 
 func (c *Client) DeleteDNSRecord(domainID uint64, recordID string) error {
-	uri := fmt.Sprintf("/1/domain/%d/dns/record/%s", domainID, recordID)
-
-	req, err := c.newRequest(http.MethodDelete, uri, nil)
+	endpoint, err := url.JoinPath(c.apiEndpoint, "1", "domain", strconv.FormatUint(domainID, 10), "dns", "record", recordID)
 	if err != nil {
 		return err
 	}
 
+	req, err := http.NewRequest(http.MethodDelete, endpoint, http.NoBody)
+	if err != nil {
+		return fmt.Errorf("failed to create request: %w", err)
+	}
+
 	_, err = c.do(req)
 
 	return err
@@ -100,17 +106,19 @@ func (c *Client) GetDomainByName(name string) (*DNSDomain, error) {
 }
 
 func (c *Client) getDomainByName(name string) (*DNSDomain, error) {
-	base, err := url.Parse("/1/product")
+	baseURL, err := url.Parse(c.apiEndpoint)
 	if err != nil {
 		return nil, err
 	}
 
-	query := base.Query()
+	endpoint := baseURL.JoinPath("1", "product")
+
+	query := endpoint.Query()
 	query.Add("service_name", "domain")
 	query.Add("customer_name", name)
-	base.RawQuery = query.Encode()
+	endpoint.RawQuery = query.Encode()
 
-	req, err := c.newRequest(http.MethodGet, base.String(), nil)
+	req, err := http.NewRequest(http.MethodGet, endpoint.String(), http.NoBody)
 	if err != nil {
 		return nil, err
 	}
@@ -135,6 +143,9 @@ func (c *Client) getDomainByName(name string) (*DNSDomain, error) {
 }
 
 func (c *Client) do(req *http.Request) (*APIResponse, error) {
+	req.Header.Set("Authorization", "Bearer "+c.apiToken)
+	req.Header.Set("Content-Type", "application/json")
+
 	rawResp, err := c.HTTPClient.Do(req)
 	if err != nil {
 		return nil, fmt.Errorf("failed to perform API request: %w", err)
@@ -149,7 +160,7 @@ func (c *Client) do(req *http.Request) (*APIResponse, error) {
 
 	var resp APIResponse
 	if err := json.Unmarshal(content, &resp); err != nil {
-		return nil, fmt.Errorf("failed to unmarshal the response body: %s", string(content))
+		return nil, fmt.Errorf("failed to unmarshal the response body: %s, %w", string(content), err)
 	}
 
 	if resp.Result != "success" {
@@ -158,25 +169,3 @@ func (c *Client) do(req *http.Request) (*APIResponse, error) {
 
 	return &resp, nil
 }
-
-func (c *Client) newRequest(method, uri string, body io.Reader) (*http.Request, error) {
-	baseURL, err := url.Parse(c.apiEndpoint)
-	if err != nil {
-		return nil, err
-	}
-
-	endpoint, err := baseURL.Parse(path.Join(baseURL.Path, uri))
-	if err != nil {
-		return nil, err
-	}
-
-	req, err := http.NewRequest(method, endpoint.String(), body)
-	if err != nil {
-		return nil, fmt.Errorf("failed to create request: %w", err)
-	}
-
-	req.Header.Set("Authorization", "Bearer "+c.apiToken)
-	req.Header.Set("Content-Type", "application/json")
-
-	return req, nil
-}
diff --git a/providers/dns/internetbs/internal/client.go b/providers/dns/internetbs/internal/client.go
index b5eebf73..9334586f 100644
--- a/providers/dns/internetbs/internal/client.go
+++ b/providers/dns/internetbs/internal/client.go
@@ -7,7 +7,6 @@ import (
 	"net/http"
 	"net/url"
 	"os"
-	"path"
 	"path/filepath"
 	"strings"
 	"time"
@@ -89,10 +88,7 @@ func (c Client) ListRecords(query ListRecordQuery) ([]Record, error) {
 }
 
 func (c Client) do(action string, params interface{}, response interface{}) error {
-	endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, "Domain", "DnsRecord", action))
-	if err != nil {
-		return fmt.Errorf("create endpoint: %w", err)
-	}
+	endpoint := c.baseURL.JoinPath("Domain", "DnsRecord", action)
 
 	values, err := querystring.Values(params)
 	if err != nil {
diff --git a/providers/dns/ionos/internal/client.go b/providers/dns/ionos/internal/client.go
index 6bee19f7..3abd5657 100644
--- a/providers/dns/ionos/internal/client.go
+++ b/providers/dns/ionos/internal/client.go
@@ -8,7 +8,6 @@ import (
 	"io"
 	"net/http"
 	"net/url"
-	"path"
 
 	querystring "github.com/google/go-querystring/query"
 )
@@ -40,7 +39,9 @@ func NewClient(apiKey string) (*Client, error) {
 
 // ListZones gets all zones.
 func (c *Client) ListZones(ctx context.Context) ([]Zone, error) {
-	req, err := c.makeRequest(ctx, http.MethodGet, "/v1/zones", nil)
+	endpoint := c.BaseURL.JoinPath("v1", "zones")
+
+	req, err := c.makeRequest(ctx, http.MethodGet, endpoint, nil)
 	if err != nil {
 		return nil, fmt.Errorf("failed to create request: %w", err)
 	}
@@ -65,14 +66,16 @@ func (c *Client) ListZones(ctx context.Context) ([]Zone, error) {
 	return zones, nil
 }
 
-// ReplaceRecords replaces the some records of a zones.
+// ReplaceRecords replaces some records of a zones.
 func (c *Client) ReplaceRecords(ctx context.Context, zoneID string, records []Record) error {
+	endpoint := c.BaseURL.JoinPath("v1", "zones", zoneID)
+
 	body, err := json.Marshal(records)
 	if err != nil {
 		return fmt.Errorf("failed to marshal request body: %w", err)
 	}
 
-	req, err := c.makeRequest(ctx, http.MethodPatch, path.Join("/v1/zones", zoneID), bytes.NewReader(body))
+	req, err := c.makeRequest(ctx, http.MethodPatch, endpoint, bytes.NewReader(body))
 	if err != nil {
 		return fmt.Errorf("failed to create request: %w", err)
 	}
@@ -93,7 +96,9 @@ func (c *Client) ReplaceRecords(ctx context.Context, zoneID string, records []Re
 
 // GetRecords gets the records of a zones.
 func (c *Client) GetRecords(ctx context.Context, zoneID string, filter *RecordsFilter) ([]Record, error) {
-	req, err := c.makeRequest(ctx, http.MethodGet, path.Join("/v1/zones", zoneID), nil)
+	endpoint := c.BaseURL.JoinPath("v1", "zones", zoneID)
+
+	req, err := c.makeRequest(ctx, http.MethodGet, endpoint, nil)
 	if err != nil {
 		return nil, fmt.Errorf("failed to create request: %w", err)
 	}
@@ -129,7 +134,9 @@ func (c *Client) GetRecords(ctx context.Context, zoneID string, filter *RecordsF
 
 // RemoveRecord removes a record.
 func (c *Client) RemoveRecord(ctx context.Context, zoneID, recordID string) error {
-	req, err := c.makeRequest(ctx, http.MethodDelete, path.Join("/v1/zones", zoneID, "records", recordID), nil)
+	endpoint := c.BaseURL.JoinPath("v1", "zones", zoneID, "records", recordID)
+
+	req, err := c.makeRequest(ctx, http.MethodDelete, endpoint, nil)
 	if err != nil {
 		return fmt.Errorf("failed to create request: %w", err)
 	}
@@ -148,12 +155,7 @@ func (c *Client) RemoveRecord(ctx context.Context, zoneID, recordID string) erro
 	return nil
 }
 
-func (c *Client) makeRequest(ctx context.Context, method, uri string, body io.Reader) (*http.Request, error) {
-	endpoint, err := c.BaseURL.Parse(path.Join(c.BaseURL.Path, uri))
-	if err != nil {
-		return nil, fmt.Errorf("failed to parse endpoint: %w", err)
-	}
-
+func (c *Client) makeRequest(ctx context.Context, method string, endpoint *url.URL, body io.Reader) (*http.Request, error) {
 	req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), body)
 	if err != nil {
 		return nil, fmt.Errorf("failed to create request: %w", err)
diff --git a/providers/dns/joker/internal/dmapi/client.go b/providers/dns/joker/internal/dmapi/client.go
index 1c8b34e9..00a84b53 100644
--- a/providers/dns/joker/internal/dmapi/client.go
+++ b/providers/dns/joker/internal/dmapi/client.go
@@ -8,7 +8,6 @@ import (
 	"io"
 	"net/http"
 	"net/url"
-	"path"
 	"strconv"
 	"strings"
 
@@ -125,12 +124,7 @@ func (c *Client) PutZone(domain, zone string) (*Response, error) {
 
 // postRequest performs actual HTTP request.
 func (c *Client) postRequest(cmd string, data url.Values) (*Response, error) {
-	baseURL, err := url.Parse(c.BaseURL)
-	if err != nil {
-		return nil, err
-	}
-
-	endpoint, err := baseURL.Parse(path.Join(baseURL.Path, cmd))
+	endpoint, err := url.JoinPath(c.BaseURL, cmd)
 	if err != nil {
 		return nil, err
 	}
@@ -140,10 +134,10 @@ func (c *Client) postRequest(cmd string, data url.Values) (*Response, error) {
 	}
 
 	if c.Debug {
-		log.Infof("postRequest:\n\tURL: %q\n\tData: %v", endpoint.String(), data)
+		log.Infof("postRequest:\n\tURL: %q\n\tData: %v", endpoint, data)
 	}
 
-	resp, err := c.HTTPClient.PostForm(endpoint.String(), data)
+	resp, err := c.HTTPClient.PostForm(endpoint, data)
 	if err != nil {
 		return nil, err
 	}
diff --git a/providers/dns/liara/internal/client.go b/providers/dns/liara/internal/client.go
index 763ca3d3..56fe2e85 100644
--- a/providers/dns/liara/internal/client.go
+++ b/providers/dns/liara/internal/client.go
@@ -7,7 +7,6 @@ import (
 	"io"
 	"net/http"
 	"net/url"
-	"path"
 	"time"
 )
 
@@ -34,10 +33,7 @@ func NewClient(apiKey string) *Client {
 // GetRecords gets the records of a domain.
 // https://dns-service.iran.liara.ir/swagger
 func (c Client) GetRecords(domainName string) ([]Record, error) {
-	endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, "api", "v1", "zones", domainName, "dns-records"))
-	if err != nil {
-		return nil, fmt.Errorf("parse URL: %w", err)
-	}
+	endpoint := c.baseURL.JoinPath("api", "v1", "zones", domainName, "dns-records")
 
 	req, err := http.NewRequest(http.MethodGet, endpoint.String(), nil)
 	if err != nil {
@@ -68,10 +64,7 @@ func (c Client) GetRecords(domainName string) ([]Record, error) {
 
 // CreateRecord creates a record.
 func (c Client) CreateRecord(domainName string, record Record) (*Record, error) {
-	endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, "api", "v1", "zones", domainName, "dns-records"))
-	if err != nil {
-		return nil, fmt.Errorf("parse URL: %w", err)
-	}
+	endpoint := c.baseURL.JoinPath("api", "v1", "zones", domainName, "dns-records")
 
 	body, err := json.Marshal(record)
 	if err != nil {
@@ -108,10 +101,7 @@ func (c Client) CreateRecord(domainName string, record Record) (*Record, error)
 
 // GetRecord gets a specific record.
 func (c Client) GetRecord(domainName, recordID string) (*Record, error) {
-	endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, "api", "v1", "zones", domainName, "dns-records", recordID))
-	if err != nil {
-		return nil, fmt.Errorf("parse URL: %w", err)
-	}
+	endpoint := c.baseURL.JoinPath("api", "v1", "zones", domainName, "dns-records", recordID)
 
 	req, err := http.NewRequest(http.MethodGet, endpoint.String(), nil)
 	if err != nil {
@@ -142,10 +132,7 @@ func (c Client) GetRecord(domainName, recordID string) (*Record, error) {
 
 // DeleteRecord deletes a record.
 func (c Client) DeleteRecord(domainName, recordID string) error {
-	endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, "api", "v1", "zones", domainName, "dns-records", recordID))
-	if err != nil {
-		return fmt.Errorf("parse URL: %w", err)
-	}
+	endpoint := c.baseURL.JoinPath("api", "v1", "zones", domainName, "dns-records", recordID)
 
 	req, err := http.NewRequest(http.MethodDelete, endpoint.String(), nil)
 	if err != nil {
diff --git a/providers/dns/mythicbeasts/client.go b/providers/dns/mythicbeasts/client.go
index 195fb196..473e4e77 100644
--- a/providers/dns/mythicbeasts/client.go
+++ b/providers/dns/mythicbeasts/client.go
@@ -7,7 +7,6 @@ import (
 	"fmt"
 	"io"
 	"net/http"
-	"path"
 	"strings"
 	"time"
 )
@@ -144,10 +143,7 @@ func (d *DNSProvider) createTXTRecord(zone, leaf, value string) error {
 		return fmt.Errorf("createTXTRecord: marshaling request body failed: %w", err)
 	}
 
-	endpoint, err := d.config.APIEndpoint.Parse(path.Join(d.config.APIEndpoint.Path, "zones", zone, "records", leaf, "TXT"))
-	if err != nil {
-		return fmt.Errorf("createTXTRecord: failed to parse URL: %w", err)
-	}
+	endpoint := d.config.APIEndpoint.JoinPath("zones", zone, "records", leaf, "TXT")
 
 	req, err := http.NewRequest(http.MethodPost, endpoint.String(), bytes.NewReader(reqBody))
 	if err != nil {
@@ -193,10 +189,7 @@ func (d *DNSProvider) removeTXTRecord(zone, leaf, value string) error {
 		return fmt.Errorf("removeTXTRecord: not logged in")
 	}
 
-	endpoint, err := d.config.APIEndpoint.Parse(path.Join(d.config.APIEndpoint.Path, "zones", zone, "records", leaf, "TXT"))
-	if err != nil {
-		return fmt.Errorf("removeTXTRecord: failed to parse URL: %w", err)
-	}
+	endpoint := d.config.APIEndpoint.JoinPath("zones", zone, "records", leaf, "TXT")
 
 	query := endpoint.Query()
 	query.Add("data", value)
diff --git a/providers/dns/nearlyfreespeech/internal/client.go b/providers/dns/nearlyfreespeech/internal/client.go
index 5641000e..a59636ae 100644
--- a/providers/dns/nearlyfreespeech/internal/client.go
+++ b/providers/dns/nearlyfreespeech/internal/client.go
@@ -8,7 +8,6 @@ import (
 	"math/rand"
 	"net/http"
 	"net/url"
-	"path"
 	"strconv"
 	"strings"
 	"time"
@@ -42,29 +41,28 @@ func NewClient(login string, apiKey string) *Client {
 }
 
 func (c Client) AddRecord(domain string, record Record) error {
+	endpoint := c.baseURL.JoinPath("dns", dns01.UnFqdn(domain), "addRR")
+
 	params, err := querystring.Values(record)
 	if err != nil {
 		return err
 	}
 
-	return c.do(path.Join("dns", dns01.UnFqdn(domain), "addRR"), params)
+	return c.do(endpoint, params)
 }
 
 func (c Client) RemoveRecord(domain string, record Record) error {
+	endpoint := c.baseURL.JoinPath("dns", dns01.UnFqdn(domain), "removeRR")
+
 	params, err := querystring.Values(record)
 	if err != nil {
 		return err
 	}
 
-	return c.do(path.Join("dns", dns01.UnFqdn(domain), "removeRR"), params)
+	return c.do(endpoint, params)
 }
 
-func (c Client) do(uri string, params url.Values) error {
-	endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, uri))
-	if err != nil {
-		return err
-	}
-
+func (c Client) do(endpoint *url.URL, params url.Values) error {
 	payload := params.Encode()
 
 	req, err := http.NewRequest(http.MethodPost, endpoint.String(), strings.NewReader(payload))
diff --git a/providers/dns/netlify/internal/client.go b/providers/dns/netlify/internal/client.go
index 1f521ca3..5955625f 100644
--- a/providers/dns/netlify/internal/client.go
+++ b/providers/dns/netlify/internal/client.go
@@ -7,7 +7,6 @@ import (
 	"io"
 	"net/http"
 	"net/url"
-	"path"
 )
 
 const defaultBaseURL = "https://api.netlify.com/api/v1"
@@ -36,7 +35,7 @@ func (c *Client) GetRecords(zoneID string) ([]DNSRecord, error) {
 		return nil, fmt.Errorf("failed to parse endpoint: %w", err)
 	}
 
-	req, err := http.NewRequest(http.MethodGet, endpoint, nil)
+	req, err := http.NewRequest(http.MethodGet, endpoint.String(), nil)
 	if err != nil {
 		return nil, fmt.Errorf("failed to create request: %w", err)
 	}
@@ -81,7 +80,7 @@ func (c *Client) CreateRecord(zoneID string, record DNSRecord) (*DNSRecord, erro
 		return nil, fmt.Errorf("failed to marshal request body: %w", err)
 	}
 
-	req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewReader(marshaledRecord))
+	req, err := http.NewRequest(http.MethodPost, endpoint.String(), bytes.NewReader(marshaledRecord))
 	if err != nil {
 		return nil, fmt.Errorf("failed to create request: %w", err)
 	}
@@ -122,7 +121,7 @@ func (c *Client) RemoveRecord(zoneID, recordID string) error {
 		return fmt.Errorf("failed to parse endpoint: %w", err)
 	}
 
-	req, err := http.NewRequest(http.MethodDelete, endpoint, nil)
+	req, err := http.NewRequest(http.MethodDelete, endpoint.String(), nil)
 	if err != nil {
 		return fmt.Errorf("failed to create request: %w", err)
 	}
@@ -149,16 +148,11 @@ func (c *Client) RemoveRecord(zoneID, recordID string) error {
 	return nil
 }
 
-func (c *Client) createEndpoint(parts ...string) (string, error) {
+func (c *Client) createEndpoint(parts ...string) (*url.URL, error) {
 	base, err := url.Parse(c.BaseURL)
 	if err != nil {
-		return "", fmt.Errorf("failed to parse base URL: %w", err)
+		return nil, fmt.Errorf("failed to parse base URL: %w", err)
 	}
 
-	endpoint, err := base.Parse(path.Join(base.Path, path.Join(parts...)))
-	if err != nil {
-		return "", fmt.Errorf("failed to parse endpoint path: %w", err)
-	}
-
-	return endpoint.String(), nil
+	return base.JoinPath(parts...), nil
 }
diff --git a/providers/dns/nicmanager/internal/client.go b/providers/dns/nicmanager/internal/client.go
index 246a8ee4..5f165662 100644
--- a/providers/dns/nicmanager/internal/client.go
+++ b/providers/dns/nicmanager/internal/client.go
@@ -7,7 +7,6 @@ import (
 	"io"
 	"net/http"
 	"net/url"
-	"path"
 	"strconv"
 	"time"
 
@@ -74,7 +73,9 @@ func NewClient(opts Options) *Client {
 }
 
 func (c Client) GetZone(name string) (*Zone, error) {
-	resp, err := c.do(http.MethodGet, name, nil)
+	endpoint := c.baseURL.JoinPath(c.mode, name)
+
+	resp, err := c.do(http.MethodGet, endpoint, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -102,7 +103,9 @@ func (c Client) GetZone(name string) (*Zone, error) {
 }
 
 func (c Client) AddRecord(zone string, req RecordCreateUpdate) error {
-	resp, err := c.do(http.MethodPost, path.Join(zone, "records"), req)
+	endpoint := c.baseURL.JoinPath(c.mode, zone, "records")
+
+	resp, err := c.do(http.MethodPost, endpoint, req)
 	if err != nil {
 		return err
 	}
@@ -124,7 +127,9 @@ func (c Client) AddRecord(zone string, req RecordCreateUpdate) error {
 }
 
 func (c Client) DeleteRecord(zone string, record int) error {
-	resp, err := c.do(http.MethodDelete, path.Join(zone, "records", strconv.Itoa(record)), nil)
+	endpoint := c.baseURL.JoinPath(c.mode, zone, "records", strconv.Itoa(record))
+
+	resp, err := c.do(http.MethodDelete, endpoint, nil)
 	if err != nil {
 		return err
 	}
@@ -145,7 +150,7 @@ func (c Client) DeleteRecord(zone string, record int) error {
 	return nil
 }
 
-func (c Client) do(method, uri string, body interface{}) (*http.Response, error) {
+func (c Client) do(method string, endpoint *url.URL, body interface{}) (*http.Response, error) {
 	var reqBody io.Reader
 	if body != nil {
 		jsonValue, err := json.Marshal(body)
@@ -156,11 +161,6 @@ func (c Client) do(method, uri string, body interface{}) (*http.Response, error)
 		reqBody = bytes.NewBuffer(jsonValue)
 	}
 
-	endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, c.mode, uri))
-	if err != nil {
-		return nil, err
-	}
-
 	r, err := http.NewRequest(method, endpoint.String(), reqBody)
 	if err != nil {
 		return nil, err
diff --git a/providers/dns/pdns/client.go b/providers/dns/pdns/client.go
index 2c98ca78..47ffff2e 100644
--- a/providers/dns/pdns/client.go
+++ b/providers/dns/pdns/client.go
@@ -200,12 +200,9 @@ func (d *DNSProvider) makeRequest(method, uri string, body io.Reader) (*http.Req
 		p = path.Join("/api", "v"+strconv.Itoa(d.apiVersion), p)
 	}
 
-	u, err := d.config.Host.Parse(path.Join(d.config.Host.Path, p))
-	if err != nil {
-		return nil, err
-	}
+	endpoint := d.config.Host.JoinPath(p)
 
-	req, err := http.NewRequest(method, u.String(), body)
+	req, err := http.NewRequest(method, strings.TrimSuffix(endpoint.String(), "/"), body)
 	if err != nil {
 		return nil, err
 	}
diff --git a/providers/dns/regru/internal/client.go b/providers/dns/regru/internal/client.go
index a5f5e503..cac3a815 100644
--- a/providers/dns/regru/internal/client.go
+++ b/providers/dns/regru/internal/client.go
@@ -6,7 +6,6 @@ import (
 	"io"
 	"net/http"
 	"net/url"
-	"path"
 )
 
 const defaultBaseURL = "https://api.reg.ru/api/regru2/"
@@ -133,10 +132,5 @@ func (c Client) createEndpoint(fragments ...string) (*url.URL, error) {
 		return nil, err
 	}
 
-	endpoint, err := baseURL.Parse(path.Join(baseURL.Path, path.Join(fragments...)))
-	if err != nil {
-		return nil, err
-	}
-
-	return endpoint, nil
+	return baseURL.JoinPath(fragments...), nil
 }
diff --git a/providers/dns/safedns/internal/client.go b/providers/dns/safedns/internal/client.go
index 2d18b569..af416ece 100644
--- a/providers/dns/safedns/internal/client.go
+++ b/providers/dns/safedns/internal/client.go
@@ -8,7 +8,6 @@ import (
 	"io"
 	"net/http"
 	"net/url"
-	"path"
 	"strconv"
 	"time"
 
@@ -42,10 +41,7 @@ func (c *Client) AddRecord(zone string, record Record) (*AddRecordResponse, erro
 		return nil, err
 	}
 
-	endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, "zones", dns01.UnFqdn(zone), "records"))
-	if err != nil {
-		return nil, err
-	}
+	endpoint := c.baseURL.JoinPath("zones", dns01.UnFqdn(zone), "records")
 
 	req, err := c.newRequest(http.MethodPost, endpoint.String(), bytes.NewReader(body))
 	if err != nil {
@@ -78,10 +74,7 @@ func (c *Client) AddRecord(zone string, record Record) (*AddRecordResponse, erro
 
 // RemoveRecord removes a DNS record.
 func (c *Client) RemoveRecord(zone string, recordID int) error {
-	endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, "zones", dns01.UnFqdn(zone), "records", strconv.Itoa(recordID)))
-	if err != nil {
-		return err
-	}
+	endpoint := c.baseURL.JoinPath("zones", dns01.UnFqdn(zone), "records", strconv.Itoa(recordID))
 
 	req, err := c.newRequest(http.MethodDelete, endpoint.String(), nil)
 	if err != nil {
diff --git a/providers/dns/simply/internal/client.go b/providers/dns/simply/internal/client.go
index a3c0af0d..a128fd47 100644
--- a/providers/dns/simply/internal/client.go
+++ b/providers/dns/simply/internal/client.go
@@ -7,7 +7,7 @@ import (
 	"fmt"
 	"net/http"
 	"net/url"
-	"path"
+	"strings"
 	"time"
 )
 
@@ -99,12 +99,9 @@ func (c *Client) DeleteRecord(zoneName string, id int64) error {
 }
 
 func (c *Client) do(zoneName string, endpoint string, reqMethod string, reqBody []byte) (*apiResponse, error) {
-	reqURL, err := c.baseURL.Parse(path.Join(c.baseURL.Path, c.accountName, c.apiKey, "my", "products", zoneName, "dns", "records", endpoint))
-	if err != nil {
-		return nil, fmt.Errorf("failed to parse endpoint: %w", err)
-	}
+	reqURL := c.baseURL.JoinPath(c.accountName, c.apiKey, "my", "products", zoneName, "dns", "records", endpoint)
 
-	req, err := http.NewRequest(reqMethod, reqURL.String(), bytes.NewReader(reqBody))
+	req, err := http.NewRequest(reqMethod, strings.TrimSuffix(reqURL.String(), "/"), bytes.NewReader(reqBody))
 	if err != nil {
 		return nil, fmt.Errorf("failed to create request: %w", err)
 	}
diff --git a/providers/dns/stackpath/client.go b/providers/dns/stackpath/client.go
index 9050a17f..f38f4e75 100644
--- a/providers/dns/stackpath/client.go
+++ b/providers/dns/stackpath/client.go
@@ -7,7 +7,6 @@ import (
 	"fmt"
 	"io"
 	"net/http"
-	"path"
 
 	"github.com/go-acme/lego/v4/challenge/dns01"
 	"golang.org/x/net/publicsuffix"
@@ -125,19 +124,10 @@ func (d *DNSProvider) deleteZoneRecord(zone *Zone, record Record) error {
 }
 
 func (d *DNSProvider) newRequest(method, urlStr string, body interface{}) (*http.Request, error) {
-	u, err := d.BaseURL.Parse(path.Join(d.config.StackID, urlStr))
-	if err != nil {
-		return nil, err
-	}
+	u := d.BaseURL.JoinPath(d.config.StackID, urlStr)
 
 	if body == nil {
-		var req *http.Request
-		req, err = http.NewRequest(method, u.String(), nil)
-		if err != nil {
-			return nil, err
-		}
-
-		return req, nil
+		return http.NewRequest(method, u.String(), nil)
 	}
 
 	reqBody, err := json.Marshal(body)
diff --git a/providers/dns/variomedia/internal/client.go b/providers/dns/variomedia/internal/client.go
index 0e3743ee..6df23dd6 100644
--- a/providers/dns/variomedia/internal/client.go
+++ b/providers/dns/variomedia/internal/client.go
@@ -7,7 +7,6 @@ import (
 	"io"
 	"net/http"
 	"net/url"
-	"path"
 	"time"
 )
 
@@ -30,10 +29,7 @@ func NewClient(apiToken string) *Client {
 }
 
 func (c Client) CreateDNSRecord(record DNSRecord) (*CreateDNSRecordResponse, error) {
-	endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, "dns-records"))
-	if err != nil {
-		return nil, err
-	}
+	endpoint := c.baseURL.JoinPath("dns-records")
 
 	data := CreateDNSRecordRequest{Data: Data{
 		Type:       "dns-record",
@@ -60,10 +56,7 @@ func (c Client) CreateDNSRecord(record DNSRecord) (*CreateDNSRecordResponse, err
 }
 
 func (c Client) DeleteDNSRecord(id string) (*DeleteRecordResponse, error) {
-	endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, "dns-records", id))
-	if err != nil {
-		return nil, err
-	}
+	endpoint := c.baseURL.JoinPath("dns-records", id)
 
 	req, err := http.NewRequest(http.MethodDelete, endpoint.String(), nil)
 	if err != nil {
@@ -80,10 +73,7 @@ func (c Client) DeleteDNSRecord(id string) (*DeleteRecordResponse, error) {
 }
 
 func (c Client) GetJob(id string) (*GetJobResponse, error) {
-	endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, "queue-jobs", id))
-	if err != nil {
-		return nil, err
-	}
+	endpoint := c.baseURL.JoinPath("queue-jobs", id)
 
 	req, err := http.NewRequest(http.MethodGet, endpoint.String(), nil)
 	if err != nil {
diff --git a/providers/dns/vercel/internal/client.go b/providers/dns/vercel/internal/client.go
index da68dcb7..cf168edf 100644
--- a/providers/dns/vercel/internal/client.go
+++ b/providers/dns/vercel/internal/client.go
@@ -8,7 +8,6 @@ import (
 	"io"
 	"net/http"
 	"net/url"
-	"path"
 	"time"
 
 	"github.com/go-acme/lego/v4/challenge/dns01"
@@ -39,10 +38,7 @@ func NewClient(authToken string, teamID string) *Client {
 // CreateRecord creates a DNS record.
 // https://vercel.com/docs/rest-api#endpoints/dns/create-a-dns-record
 func (c *Client) CreateRecord(zone string, record Record) (*CreateRecordResponse, error) {
-	endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, "v2", "domains", dns01.UnFqdn(zone), "records"))
-	if err != nil {
-		return nil, err
-	}
+	endpoint := c.baseURL.JoinPath("v2", "domains", dns01.UnFqdn(zone), "records")
 
 	body, err := json.Marshal(record)
 	if err != nil {
@@ -82,10 +78,7 @@ func (c *Client) CreateRecord(zone string, record Record) (*CreateRecordResponse
 // DeleteRecord deletes a DNS record.
 // https://vercel.com/docs/rest-api#endpoints/dns/delete-a-dns-record
 func (c *Client) DeleteRecord(zone string, recordID string) error {
-	endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, "v2", "domains", dns01.UnFqdn(zone), "records", recordID))
-	if err != nil {
-		return err
-	}
+	endpoint := c.baseURL.JoinPath("v2", "domains", dns01.UnFqdn(zone), "records", recordID)
 
 	req, err := c.newRequest(http.MethodDelete, endpoint.String(), nil)
 	if err != nil {
diff --git a/providers/dns/versio/client.go b/providers/dns/versio/client.go
index 7fd7e75f..b7ca67da 100644
--- a/providers/dns/versio/client.go
+++ b/providers/dns/versio/client.go
@@ -6,7 +6,6 @@ import (
 	"fmt"
 	"io"
 	"net/http"
-	"path"
 )
 
 const defaultBaseURL = "https://www.versio.nl/api/v1/"
@@ -43,7 +42,9 @@ func (d *DNSProvider) postDNSRecords(domain string, msg interface{}) error {
 		return err
 	}
 
-	req, err := d.makeRequest(http.MethodPost, "domains/"+domain+"/update", reqBody)
+	endpoint := d.config.BaseURL.JoinPath("domains", domain, "update")
+
+	req, err := http.NewRequest(http.MethodPost, endpoint.String(), reqBody)
 	if err != nil {
 		return err
 	}
@@ -52,7 +53,13 @@ func (d *DNSProvider) postDNSRecords(domain string, msg interface{}) error {
 }
 
 func (d *DNSProvider) getDNSRecords(domain string) (*dnsRecordsResponse, error) {
-	req, err := d.makeRequest(http.MethodGet, "domains/"+domain+"?show_dns_records=true", nil)
+	endpoint := d.config.BaseURL.JoinPath("domains", domain)
+
+	query := endpoint.Query()
+	query.Set("show_dns_records", "true")
+	endpoint.RawQuery = query.Encode()
+
+	req, err := http.NewRequest(http.MethodGet, endpoint.String(), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -67,27 +74,13 @@ func (d *DNSProvider) getDNSRecords(domain string) (*dnsRecordsResponse, error)
 	return respData, nil
 }
 
-func (d *DNSProvider) makeRequest(method, uri string, body io.Reader) (*http.Request, error) {
-	endpoint, err := d.config.BaseURL.Parse(path.Join(d.config.BaseURL.EscapedPath(), uri))
-	if err != nil {
-		return nil, err
-	}
-
-	req, err := http.NewRequest(method, endpoint.String(), body)
-	if err != nil {
-		return nil, err
-	}
-
+func (d *DNSProvider) do(req *http.Request, result interface{}) error {
 	req.Header.Set("Content-Type", "application/json")
 
 	if len(d.config.Username) > 0 && len(d.config.Password) > 0 {
 		req.SetBasicAuth(d.config.Username, d.config.Password)
 	}
 
-	return req, nil
-}
-
-func (d *DNSProvider) do(req *http.Request, result interface{}) error {
 	resp, err := d.config.HTTPClient.Do(req)
 	if resp != nil {
 		defer resp.Body.Close()
diff --git a/providers/dns/vkcloud/internal/client.go b/providers/dns/vkcloud/internal/client.go
index ce4af7ba..a76293c3 100644
--- a/providers/dns/vkcloud/internal/client.go
+++ b/providers/dns/vkcloud/internal/client.go
@@ -5,8 +5,6 @@ import (
 	"fmt"
 	"net/http"
 	"net/url"
-	"path"
-	"strings"
 
 	"github.com/gophercloud/gophercloud"
 	"github.com/gophercloud/gophercloud/openstack"
@@ -48,8 +46,7 @@ func (c *Client) ListZones() ([]DNSZone, error) {
 	var zones []DNSZone
 	opts := &gophercloud.RequestOpts{JSONResponse: &zones}
 
-	// TODO(ldez): go1.19 => c.baseURL.JoinPath("/")
-	endpoint := joinPath(c.baseURL, "/")
+	endpoint := c.baseURL.JoinPath("/")
 
 	err := c.request(http.MethodGet, endpoint, opts)
 	if err != nil {
@@ -63,8 +60,7 @@ func (c *Client) ListTXTRecords(zoneUUID string) ([]DNSTXTRecord, error) {
 	var records []DNSTXTRecord
 	opts := &gophercloud.RequestOpts{JSONResponse: &records}
 
-	// TODO(ldez): go1.19 => c.baseURL.JoinPath(zoneUUID, "txt", "/")
-	endpoint := joinPath(c.baseURL, zoneUUID, "txt", "/")
+	endpoint := c.baseURL.JoinPath(zoneUUID, "txt", "/")
 
 	err := c.request(http.MethodGet, endpoint, opts)
 	if err != nil {
@@ -80,15 +76,13 @@ func (c *Client) CreateTXTRecord(zoneUUID string, record *DNSTXTRecord) error {
 		JSONResponse: record,
 	}
 
-	// TODO(ldez): go1.19 => c.baseURL.JoinPath(zoneUUID, "txt", "/")
-	endpoint := joinPath(c.baseURL, zoneUUID, "txt", "/")
+	endpoint := c.baseURL.JoinPath(zoneUUID, "txt", "/")
 
 	return c.request(http.MethodPost, endpoint, opts)
 }
 
 func (c *Client) DeleteTXTRecord(zoneUUID, recordUUID string) error {
-	// TODO(ldez): go1.19 => c.baseURL.JoinPath(zoneUUID, "txt", recordUUID)
-	endpoint := joinPath(c.baseURL, zoneUUID, "txt", recordUUID)
+	endpoint := c.baseURL.JoinPath(zoneUUID, "txt", recordUUID)
 
 	return c.request(http.MethodDelete, endpoint, &gophercloud.RequestOpts{})
 }
@@ -144,17 +138,3 @@ func validateAuthOptions(opts gophercloud.AuthOptions) error {
 
 	return nil
 }
-
-// light version of go1.19 url.URL.JoinPath.
-// TODO(ldez): must be remove when we will update to go1.19.
-func joinPath(uri *url.URL, elem ...string) *url.URL {
-	result := path.Join(elem...)
-	result = path.Join(uri.Path, result)
-	if len(elem) > 0 && strings.HasSuffix(elem[len(elem)-1], "/") {
-		result += "/"
-	}
-
-	parse, _ := uri.Parse(result)
-
-	return parse
-}
diff --git a/providers/dns/websupport/internal/client.go b/providers/dns/websupport/internal/client.go
index 1dc2d42b..4cb803a4 100644
--- a/providers/dns/websupport/internal/client.go
+++ b/providers/dns/websupport/internal/client.go
@@ -11,7 +11,6 @@ import (
 	"io"
 	"net/http"
 	"net/url"
-	"path"
 	"strconv"
 	"time"
 )
@@ -46,17 +45,12 @@ func NewClient(apiKey, secretKey string) (*Client, error) {
 // GetUser gets a user detail.
 // https://rest.websupport.sk/docs/v1.user#user
 func (c *Client) GetUser(userID string) (*User, error) {
-	baseURL, err := url.Parse(c.BaseURL)
+	endpoint, err := url.JoinPath(c.BaseURL, "v1", "user", userID)
 	if err != nil {
 		return nil, fmt.Errorf("base url parsing: %w", err)
 	}
 
-	endpoint, err := baseURL.Parse(path.Join(baseURL.Path, "v1", "user", userID))
-	if err != nil {
-		return nil, fmt.Errorf("failed to parse endpoint: %w", err)
-	}
-
-	req, err := http.NewRequest(http.MethodGet, endpoint.String(), http.NoBody)
+	req, err := http.NewRequest(http.MethodGet, endpoint, http.NoBody)
 	if err != nil {
 		return nil, fmt.Errorf("request payload: %w", err)
 	}
@@ -74,17 +68,12 @@ func (c *Client) GetUser(userID string) (*User, error) {
 // ListRecords lists all records.
 // https://rest.websupport.sk/docs/v1.zone#records
 func (c *Client) ListRecords(domainName string) (*ListResponse, error) {
-	baseURL, err := url.Parse(c.BaseURL)
+	endpoint, err := url.JoinPath(c.BaseURL, "v1", "user", "self", "zone", domainName, "record")
 	if err != nil {
 		return nil, fmt.Errorf("base url parsing: %w", err)
 	}
 
-	endpoint, err := baseURL.Parse(path.Join(baseURL.Path, "v1", "user", "self", "zone", domainName, "record"))
-	if err != nil {
-		return nil, fmt.Errorf("failed to parse endpoint: %w", err)
-	}
-
-	req, err := http.NewRequest(http.MethodGet, endpoint.String(), http.NoBody)
+	req, err := http.NewRequest(http.MethodGet, endpoint, http.NoBody)
 	if err != nil {
 		return nil, fmt.Errorf("request payload: %w", err)
 	}
@@ -101,17 +90,12 @@ func (c *Client) ListRecords(domainName string) (*ListResponse, error) {
 
 // GetRecords gets a DNS record.
 func (c *Client) GetRecords(domainName string, recordID int) (*Record, error) {
-	baseURL, err := url.Parse(c.BaseURL)
+	endpoint, err := url.JoinPath(c.BaseURL, "v1", "user", "self", "zone", domainName, "record", strconv.Itoa(recordID))
 	if err != nil {
 		return nil, fmt.Errorf("base url parsing: %w", err)
 	}
 
-	endpoint, err := baseURL.Parse(path.Join(baseURL.Path, "v1", "user", "self", "zone", domainName, "record", strconv.Itoa(recordID)))
-	if err != nil {
-		return nil, fmt.Errorf("failed to parse endpoint: %w", err)
-	}
-
-	req, err := http.NewRequest(http.MethodGet, endpoint.String(), http.NoBody)
+	req, err := http.NewRequest(http.MethodGet, endpoint, http.NoBody)
 	if err != nil {
 		return nil, err
 	}
@@ -129,22 +113,17 @@ func (c *Client) GetRecords(domainName string, recordID int) (*Record, error) {
 // AddRecord adds a DNS record.
 // https://rest.websupport.sk/docs/v1.zone#post-record
 func (c *Client) AddRecord(domainName string, record Record) (*Response, error) {
-	baseURL, err := url.Parse(c.BaseURL)
+	endpoint, err := url.JoinPath(c.BaseURL, "v1", "user", "self", "zone", domainName, "record")
 	if err != nil {
 		return nil, fmt.Errorf("base url parsing: %w", err)
 	}
 
-	endpoint, err := baseURL.Parse(path.Join(baseURL.Path, "v1", "user", "self", "zone", domainName, "record"))
-	if err != nil {
-		return nil, fmt.Errorf("failed to parse endpoint: %w", err)
-	}
-
 	payload, err := json.Marshal(record)
 	if err != nil {
 		return nil, fmt.Errorf("request payload: %w", err)
 	}
 
-	req, err := http.NewRequest(http.MethodPost, endpoint.String(), bytes.NewReader(payload))
+	req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewReader(payload))
 	if err != nil {
 		return nil, err
 	}
@@ -162,17 +141,12 @@ func (c *Client) AddRecord(domainName string, record Record) (*Response, error)
 // DeleteRecord deletes a DNS record.
 // https://rest.websupport.sk/docs/v1.zone#delete-record
 func (c *Client) DeleteRecord(domainName string, recordID int) (*Response, error) {
-	baseURL, err := url.Parse(c.BaseURL)
+	endpoint, err := url.JoinPath(c.BaseURL, "v1", "user", "self", "zone", domainName, "record", strconv.Itoa(recordID))
 	if err != nil {
 		return nil, fmt.Errorf("base url parsing: %w", err)
 	}
 
-	endpoint, err := baseURL.Parse(path.Join(baseURL.Path, "v1", "user", "self", "zone", domainName, "record", strconv.Itoa(recordID)))
-	if err != nil {
-		return nil, fmt.Errorf("failed to parse endpoint: %w", err)
-	}
-
-	req, err := http.NewRequest(http.MethodDelete, endpoint.String(), http.NoBody)
+	req, err := http.NewRequest(http.MethodDelete, endpoint, http.NoBody)
 	if err != nil {
 		return nil, fmt.Errorf("request payload: %w", err)
 	}
diff --git a/providers/dns/zoneee/client.go b/providers/dns/zoneee/client.go
index c7c2a6da..a2f340d6 100644
--- a/providers/dns/zoneee/client.go
+++ b/providers/dns/zoneee/client.go
@@ -6,7 +6,6 @@ import (
 	"fmt"
 	"io"
 	"net/http"
-	"path"
 )
 
 const defaultEndpoint = "https://api.zone.eu/v2/dns/"
@@ -32,7 +31,9 @@ func (d *DNSProvider) addTxtRecord(domain string, record txtRecord) ([]txtRecord
 		return nil, err
 	}
 
-	req, err := d.makeRequest(http.MethodPost, path.Join(domain, "txt"), reqBody)
+	endpoint := d.config.Endpoint.JoinPath(domain, "txt")
+
+	req, err := http.NewRequest(http.MethodPost, endpoint.String(), reqBody)
 	if err != nil {
 		return nil, err
 	}
@@ -45,7 +46,9 @@ func (d *DNSProvider) addTxtRecord(domain string, record txtRecord) ([]txtRecord
 }
 
 func (d *DNSProvider) getTxtRecords(domain string) ([]txtRecord, error) {
-	req, err := d.makeRequest(http.MethodGet, path.Join(domain, "txt"), nil)
+	endpoint := d.config.Endpoint.JoinPath(domain, "txt")
+
+	req, err := http.NewRequest(http.MethodGet, endpoint.String(), http.NoBody)
 	if err != nil {
 		return nil, err
 	}
@@ -58,7 +61,9 @@ func (d *DNSProvider) getTxtRecords(domain string) ([]txtRecord, error) {
 }
 
 func (d *DNSProvider) removeTxtRecord(domain, id string) error {
-	req, err := d.makeRequest(http.MethodDelete, path.Join(domain, "txt", id), nil)
+	endpoint := d.config.Endpoint.JoinPath(domain, "txt", id)
+
+	req, err := http.NewRequest(http.MethodDelete, endpoint.String(), http.NoBody)
 	if err != nil {
 		return err
 	}
@@ -66,24 +71,10 @@ func (d *DNSProvider) removeTxtRecord(domain, id string) error {
 	return d.sendRequest(req, nil)
 }
 
-func (d *DNSProvider) makeRequest(method, resource string, body io.Reader) (*http.Request, error) {
-	uri, err := d.config.Endpoint.Parse(resource)
-	if err != nil {
-		return nil, err
-	}
-
-	req, err := http.NewRequest(method, uri.String(), body)
-	if err != nil {
-		return nil, err
-	}
-
+func (d *DNSProvider) sendRequest(req *http.Request, result interface{}) error {
 	req.Header.Set("Content-Type", "application/json")
 	req.SetBasicAuth(d.config.Username, d.config.APIKey)
 
-	return req, nil
-}
-
-func (d *DNSProvider) sendRequest(req *http.Request, result interface{}) error {
 	resp, err := d.config.HTTPClient.Do(req)
 	if err != nil {
 		return err


Test patch:
diff --git a/providers/dns/infomaniak/internal/client_test.go b/providers/dns/infomaniak/internal/client_test.go
index a6034025..7dd8c9e2 100644
--- a/providers/dns/infomaniak/internal/client_test.go
+++ b/providers/dns/infomaniak/internal/client_test.go
@@ -102,20 +102,20 @@ func TestClient_GetDomainByName(t *testing.T) {
 		}
 
 		response := `
-{
-  "result": "success",
-  "data": [
-    {
-      "id": 123,
-      "customer_name": "two.three.example.com"
-    },
-    {
-      "id": 456,
-      "customer_name": "three.example.com"
-    }
-  ]
-}
-`
+	{
+	  "result": "success",
+	  "data": [
+	    {
+	      "id": 123,
+	      "customer_name": "two.three.example.com"
+	    },
+	    {
+	      "id": 456,
+	      "customer_name": "three.example.com"
+	    }
+	  ]
+	}
+	`
 
 		_, err := rw.Write([]byte(response))
 		if err != nil {