diff --git a/cli.go b/cli.go
index e4d0fd0e..53c83d83 100644
--- a/cli.go
+++ b/cli.go
@@ -219,6 +219,7 @@ Here is an example bash command using the CloudFlare DNS provider:
 	fmt.Fprintln(w, "\tmanual:\tnone")
 	fmt.Fprintln(w, "\tnamecheap:\tNAMECHEAP_API_USER, NAMECHEAP_API_KEY")
 	fmt.Fprintln(w, "\tnamedotcom:\tNAMECOM_USERNAME, NAMECOM_API_TOKEN")
+	fmt.Fprintln(w, "\tnetcup:\tNETCUP_CUSTOMER_NUMBER, NETCUP_API_KEY, NETCUP_API_PASSWORD")
 	fmt.Fprintln(w, "\tnifcloud:\tNIFCLOUD_ACCESS_KEY_ID, NIFCLOUD_SECRET_ACCESS_KEY")
 	fmt.Fprintln(w, "\trackspace:\tRACKSPACE_USER, RACKSPACE_API_KEY")
 	fmt.Fprintln(w, "\trfc2136:\tRFC2136_TSIG_KEY, RFC2136_TSIG_SECRET,\n\t\tRFC2136_TSIG_ALGORITHM, RFC2136_NAMESERVER")
diff --git a/providers/dns/dns_providers.go b/providers/dns/dns_providers.go
index 973b8053..e932e77c 100644
--- a/providers/dns/dns_providers.go
+++ b/providers/dns/dns_providers.go
@@ -29,6 +29,7 @@ import (
 	"github.com/xenolf/lego/providers/dns/linode"
 	"github.com/xenolf/lego/providers/dns/namecheap"
 	"github.com/xenolf/lego/providers/dns/namedotcom"
+	"github.com/xenolf/lego/providers/dns/netcup"
 	"github.com/xenolf/lego/providers/dns/nifcloud"
 	"github.com/xenolf/lego/providers/dns/ns1"
 	"github.com/xenolf/lego/providers/dns/otc"
@@ -95,6 +96,8 @@ func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error)
 		return namecheap.NewDNSProvider()
 	case "namedotcom":
 		return namedotcom.NewDNSProvider()
+	case "netcup":
+		return netcup.NewDNSProvider()
 	case "nifcloud":
 		return nifcloud.NewDNSProvider()
 	case "rackspace":
diff --git a/providers/dns/netcup/client.go b/providers/dns/netcup/client.go
new file mode 100644
index 00000000..e498d694
--- /dev/null
+++ b/providers/dns/netcup/client.go
@@ -0,0 +1,327 @@
+package netcup
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+
+	"github.com/xenolf/lego/acme"
+)
+
+// netcupBaseURL for reaching the jSON-based API-Endpoint of netcup
+const netcupBaseURL = "https://ccp.netcup.net/run/webservice/servers/endpoint.php?JSON"
+
+// success response status
+const success = "success"
+
+// Request wrapper as specified in netcup wiki
+// needed for every request to netcup API around *Msg
+// https://www.netcup-wiki.de/wiki/CCP_API#Anmerkungen_zu_JSON-Requests
+type Request struct {
+	Action string      `json:"action"`
+	Param  interface{} `json:"param"`
+}
+
+// LoginMsg as specified in netcup WSDL
+// https://ccp.netcup.net/run/webservice/servers/endpoint.php#login
+type LoginMsg struct {
+	CustomerNumber  string `json:"customernumber"`
+	APIKey          string `json:"apikey"`
+	APIPassword     string `json:"apipassword"`
+	ClientRequestID string `json:"clientrequestid,omitempty"`
+}
+
+// LogoutMsg as specified in netcup WSDL
+// https://ccp.netcup.net/run/webservice/servers/endpoint.php#logout
+type LogoutMsg struct {
+	CustomerNumber  string `json:"customernumber"`
+	APIKey          string `json:"apikey"`
+	APISessionID    string `json:"apisessionid"`
+	ClientRequestID string `json:"clientrequestid,omitempty"`
+}
+
+// UpdateDNSRecordsMsg as specified in netcup WSDL
+// https://ccp.netcup.net/run/webservice/servers/endpoint.php#updateDnsRecords
+type UpdateDNSRecordsMsg struct {
+	DomainName      string       `json:"domainname"`
+	CustomerNumber  string       `json:"customernumber"`
+	APIKey          string       `json:"apikey"`
+	APISessionID    string       `json:"apisessionid"`
+	ClientRequestID string       `json:"clientrequestid,omitempty"`
+	DNSRecordSet    DNSRecordSet `json:"dnsrecordset"`
+}
+
+// DNSRecordSet as specified in netcup WSDL
+// needed in UpdateDNSRecordsMsg
+// https://ccp.netcup.net/run/webservice/servers/endpoint.php#Dnsrecordset
+type DNSRecordSet struct {
+	DNSRecords []DNSRecord `json:"dnsrecords"`
+}
+
+// InfoDNSRecordsMsg as specified in netcup WSDL
+// https://ccp.netcup.net/run/webservice/servers/endpoint.php#infoDnsRecords
+type InfoDNSRecordsMsg struct {
+	DomainName      string `json:"domainname"`
+	CustomerNumber  string `json:"customernumber"`
+	APIKey          string `json:"apikey"`
+	APISessionID    string `json:"apisessionid"`
+	ClientRequestID string `json:"clientrequestid,omitempty"`
+}
+
+// DNSRecord as specified in netcup WSDL
+// https://ccp.netcup.net/run/webservice/servers/endpoint.php#Dnsrecord
+type DNSRecord struct {
+	ID           int    `json:"id,string,omitempty"`
+	Hostname     string `json:"hostname"`
+	RecordType   string `json:"type"`
+	Priority     string `json:"priority,omitempty"`
+	Destination  string `json:"destination"`
+	DeleteRecord bool   `json:"deleterecord,omitempty"`
+	State        string `json:"state,omitempty"`
+}
+
+// ResponseMsg as specified in netcup WSDL
+// https://ccp.netcup.net/run/webservice/servers/endpoint.php#Responsemessage
+type ResponseMsg struct {
+	ServerRequestID string       `json:"serverrequestid"`
+	ClientRequestID string       `json:"clientrequestid,omitempty"`
+	Action          string       `json:"action"`
+	Status          string       `json:"status"`
+	StatusCode      int          `json:"statuscode"`
+	ShortMessage    string       `json:"shortmessage"`
+	LongMessage     string       `json:"longmessage"`
+	ResponseData    ResponseData `json:"responsedata,omitempty"`
+}
+
+// LogoutResponseMsg similar to ResponseMsg
+// allows empty ResponseData field whilst unmarshaling
+type LogoutResponseMsg struct {
+	ServerRequestID string `json:"serverrequestid"`
+	ClientRequestID string `json:"clientrequestid,omitempty"`
+	Action          string `json:"action"`
+	Status          string `json:"status"`
+	StatusCode      int    `json:"statuscode"`
+	ShortMessage    string `json:"shortmessage"`
+	LongMessage     string `json:"longmessage"`
+	ResponseData    string `json:"responsedata,omitempty"`
+}
+
+// ResponseData to enable correct unmarshaling of ResponseMsg
+type ResponseData struct {
+	APISessionID string      `json:"apisessionid"`
+	DNSRecords   []DNSRecord `json:"dnsrecords"`
+}
+
+// Client netcup DNS client
+type Client struct {
+	customerNumber string
+	apiKey         string
+	apiPassword    string
+	client         *http.Client
+}
+
+// NewClient creates a netcup DNS client
+func NewClient(httpClient *http.Client, customerNumber string, apiKey string, apiPassword string) *Client {
+	client := http.DefaultClient
+	if httpClient != nil {
+		client = httpClient
+	}
+
+	return &Client{
+		customerNumber: customerNumber,
+		apiKey:         apiKey,
+		apiPassword:    apiPassword,
+		client:         client,
+	}
+}
+
+// Login performs the login as specified by the netcup WSDL
+// returns sessionID needed to perform remaining actions
+// https://ccp.netcup.net/run/webservice/servers/endpoint.php
+func (c *Client) Login() (string, error) {
+	payload := &Request{
+		Action: "login",
+		Param: &LoginMsg{
+			CustomerNumber:  c.customerNumber,
+			APIKey:          c.apiKey,
+			APIPassword:     c.apiPassword,
+			ClientRequestID: "",
+		},
+	}
+
+	response, err := c.sendRequest(payload)
+	if err != nil {
+		return "", fmt.Errorf("netcup: error sending request to DNS-API, %v", err)
+	}
+
+	var r ResponseMsg
+
+	err = json.Unmarshal(response, &r)
+	if err != nil {
+		return "", fmt.Errorf("netcup: error decoding response of DNS-API, %v", err)
+	}
+	if r.Status != success {
+		return "", fmt.Errorf("netcup: error logging into DNS-API, %v", r.LongMessage)
+	}
+	return r.ResponseData.APISessionID, nil
+}
+
+// Logout performs the logout with the supplied sessionID as specified by the netcup WSDL
+// https://ccp.netcup.net/run/webservice/servers/endpoint.php
+func (c *Client) Logout(sessionID string) error {
+	payload := &Request{
+		Action: "logout",
+		Param: &LogoutMsg{
+			CustomerNumber:  c.customerNumber,
+			APIKey:          c.apiKey,
+			APISessionID:    sessionID,
+			ClientRequestID: "",
+		},
+	}
+
+	response, err := c.sendRequest(payload)
+	if err != nil {
+		return fmt.Errorf("netcup: error logging out of DNS-API: %v", err)
+	}
+
+	var r LogoutResponseMsg
+
+	err = json.Unmarshal(response, &r)
+	if err != nil {
+		return fmt.Errorf("netcup: error logging out of DNS-API: %v", err)
+	}
+
+	if r.Status != success {
+		return fmt.Errorf("netcup: error logging out of DNS-API: %v", r.ShortMessage)
+	}
+	return nil
+}
+
+// UpdateDNSRecord performs an update of the DNSRecords as specified by the netcup WSDL
+// https://ccp.netcup.net/run/webservice/servers/endpoint.php
+func (c *Client) UpdateDNSRecord(sessionID, domainName string, record DNSRecord) error {
+	payload := &Request{
+		Action: "updateDnsRecords",
+		Param: UpdateDNSRecordsMsg{
+			DomainName:      domainName,
+			CustomerNumber:  c.customerNumber,
+			APIKey:          c.apiKey,
+			APISessionID:    sessionID,
+			ClientRequestID: "",
+			DNSRecordSet:    DNSRecordSet{DNSRecords: []DNSRecord{record}},
+		},
+	}
+
+	response, err := c.sendRequest(payload)
+	if err != nil {
+		return fmt.Errorf("netcup: %v", err)
+	}
+
+	var r ResponseMsg
+
+	err = json.Unmarshal(response, &r)
+	if err != nil {
+		return fmt.Errorf("netcup: %v", err)
+	}
+
+	if r.Status != success {
+		return fmt.Errorf("netcup: %s: %+v", r.ShortMessage, r)
+	}
+	return nil
+}
+
+// GetDNSRecords retrieves all dns records of an DNS-Zone as specified by the netcup WSDL
+// returns an array of DNSRecords
+// https://ccp.netcup.net/run/webservice/servers/endpoint.php
+func (c *Client) GetDNSRecords(hostname, apiSessionID string) ([]DNSRecord, error) {
+	payload := &Request{
+		Action: "infoDnsRecords",
+		Param: InfoDNSRecordsMsg{
+			DomainName:      hostname,
+			CustomerNumber:  c.customerNumber,
+			APIKey:          c.apiKey,
+			APISessionID:    apiSessionID,
+			ClientRequestID: "",
+		},
+	}
+
+	response, err := c.sendRequest(payload)
+	if err != nil {
+		return nil, fmt.Errorf("netcup: %v", err)
+	}
+
+	var r ResponseMsg
+
+	err = json.Unmarshal(response, &r)
+	if err != nil {
+		return nil, fmt.Errorf("netcup: %v", err)
+	}
+
+	if r.Status != success {
+		return nil, fmt.Errorf("netcup: %s", r.ShortMessage)
+	}
+	return r.ResponseData.DNSRecords, nil
+
+}
+
+// sendRequest marshals given body to JSON, send the request to netcup API
+// and returns body of response
+func (c *Client) sendRequest(payload interface{}) ([]byte, error) {
+	body, err := json.Marshal(payload)
+	if err != nil {
+		return nil, fmt.Errorf("netcup: %v", err)
+	}
+
+	req, err := http.NewRequest(http.MethodPost, netcupBaseURL, bytes.NewReader(body))
+	if err != nil {
+		return nil, fmt.Errorf("netcup: %v", err)
+	}
+	req.Close = true
+
+	req.Header.Set("content-type", "application/json")
+	req.Header.Set("User-Agent", acme.UserAgent)
+
+	resp, err := c.client.Do(req)
+	if err != nil {
+		return nil, fmt.Errorf("netcup: %v", err)
+	}
+
+	if resp.StatusCode > 299 {
+		return nil, fmt.Errorf("netcup: API request failed with HTTP Status code %d", resp.StatusCode)
+	}
+
+	body, err = ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return nil, fmt.Errorf("netcup: read of response body failed, %v", err)
+	}
+	defer resp.Body.Close()
+
+	return body, nil
+}
+
+// GetDNSRecordIdx searches a given array of DNSRecords for a given DNSRecord
+// equivalence is determined by Destination and RecortType attributes
+// returns index of given DNSRecord in given array of DNSRecords
+func GetDNSRecordIdx(records []DNSRecord, record DNSRecord) (int, error) {
+	for index, element := range records {
+		if record.Destination == element.Destination && record.RecordType == element.RecordType {
+			return index, nil
+		}
+	}
+	return -1, fmt.Errorf("netcup: no DNS Record found")
+}
+
+// CreateTxtRecord uses the supplied values to return a DNSRecord of type TXT for the dns-01 challenge
+func CreateTxtRecord(hostname, value string) DNSRecord {
+	return DNSRecord{
+		ID:           0,
+		Hostname:     hostname,
+		RecordType:   "TXT",
+		Priority:     "",
+		Destination:  value,
+		DeleteRecord: false,
+		State:        "",
+	}
+}
diff --git a/providers/dns/netcup/client_test.go b/providers/dns/netcup/client_test.go
new file mode 100644
index 00000000..6fd446c4
--- /dev/null
+++ b/providers/dns/netcup/client_test.go
@@ -0,0 +1,220 @@
+package netcup
+
+import (
+	"fmt"
+	"net/http"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/xenolf/lego/acme"
+)
+
+func TestClientAuth(t *testing.T) {
+	if !testLive {
+		t.Skip("skipping live test")
+	}
+
+	// Setup
+	httpClient := &http.Client{
+		Timeout: 10 * time.Second,
+	}
+	client := NewClient(httpClient, testCustomerNumber, testAPIKey, testAPIPassword)
+
+	for i := 1; i < 4; i++ {
+		i := i
+		t.Run("Test"+string(i), func(t *testing.T) {
+			t.Parallel()
+
+			sessionID, err := client.Login()
+			assert.NoError(t, err)
+
+			err = client.Logout(sessionID)
+			assert.NoError(t, err)
+		})
+	}
+
+}
+
+func TestClientGetDnsRecords(t *testing.T) {
+	if !testLive {
+		t.Skip("skipping live test")
+	}
+
+	httpClient := &http.Client{
+		Timeout: 10 * time.Second,
+	}
+	client := NewClient(httpClient, testCustomerNumber, testAPIKey, testAPIPassword)
+
+	// Setup
+	sessionID, err := client.Login()
+	assert.NoError(t, err)
+
+	fqdn, _, _ := acme.DNS01Record(testDomain, "123d==")
+
+	zone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
+	assert.NoError(t, err, "error finding DNSZone")
+
+	zone = acme.UnFqdn(zone)
+
+	// TestMethod
+	_, err = client.GetDNSRecords(zone, sessionID)
+	assert.NoError(t, err)
+
+	// Tear down
+	err = client.Logout(sessionID)
+	assert.NoError(t, err)
+}
+
+func TestClientUpdateDnsRecord(t *testing.T) {
+	if !testLive {
+		t.Skip("skipping live test")
+	}
+
+	// Setup
+	httpClient := &http.Client{
+		Timeout: 10 * time.Second,
+	}
+	client := NewClient(httpClient, testCustomerNumber, testAPIKey, testAPIPassword)
+
+	sessionID, err := client.Login()
+	assert.NoError(t, err)
+
+	fqdn, _, _ := acme.DNS01Record(testDomain, "123d==")
+
+	zone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
+	assert.NoError(t, err, fmt.Errorf("error finding DNSZone, %v", err))
+
+	hostname := strings.Replace(fqdn, "."+zone, "", 1)
+
+	record := CreateTxtRecord(hostname, "asdf5678")
+
+	// test
+	zone = acme.UnFqdn(zone)
+
+	err = client.UpdateDNSRecord(sessionID, zone, record)
+	assert.NoError(t, err)
+
+	records, err := client.GetDNSRecords(zone, sessionID)
+	assert.NoError(t, err)
+
+	recordIdx, err := GetDNSRecordIdx(records, record)
+	assert.NoError(t, err)
+
+	assert.Equal(t, record.Hostname, records[recordIdx].Hostname)
+	assert.Equal(t, record.RecordType, records[recordIdx].RecordType)
+	assert.Equal(t, record.Destination, records[recordIdx].Destination)
+	assert.Equal(t, record.DeleteRecord, records[recordIdx].DeleteRecord)
+
+	records[recordIdx].DeleteRecord = true
+
+	// Tear down
+	err = client.UpdateDNSRecord(sessionID, testDomain, records[recordIdx])
+	assert.NoError(t, err, "Did not remove record! Please do so yourself.")
+
+	err = client.Logout(sessionID)
+	assert.NoError(t, err)
+}
+
+func TestClientGetDnsRecordIdx(t *testing.T) {
+	records := []DNSRecord{
+		{
+			ID:           12345,
+			Hostname:     "asdf",
+			RecordType:   "TXT",
+			Priority:     "0",
+			Destination:  "randomtext",
+			DeleteRecord: false,
+			State:        "yes",
+		},
+		{
+			ID:           23456,
+			Hostname:     "@",
+			RecordType:   "A",
+			Priority:     "0",
+			Destination:  "127.0.0.1",
+			DeleteRecord: false,
+			State:        "yes",
+		},
+		{
+			ID:           34567,
+			Hostname:     "dfgh",
+			RecordType:   "CNAME",
+			Priority:     "0",
+			Destination:  "example.com",
+			DeleteRecord: false,
+			State:        "yes",
+		},
+		{
+			ID:           45678,
+			Hostname:     "fghj",
+			RecordType:   "MX",
+			Priority:     "10",
+			Destination:  "mail.example.com",
+			DeleteRecord: false,
+			State:        "yes",
+		},
+	}
+
+	testCases := []struct {
+		desc        string
+		record      DNSRecord
+		expectError bool
+	}{
+		{
+			desc: "simple",
+			record: DNSRecord{
+				ID:           12345,
+				Hostname:     "asdf",
+				RecordType:   "TXT",
+				Priority:     "0",
+				Destination:  "randomtext",
+				DeleteRecord: false,
+				State:        "yes",
+			},
+		},
+		{
+			desc: "wrong Destination",
+			record: DNSRecord{
+				ID:           12345,
+				Hostname:     "asdf",
+				RecordType:   "TXT",
+				Priority:     "0",
+				Destination:  "wrong",
+				DeleteRecord: false,
+				State:        "yes",
+			},
+			expectError: true,
+		},
+		{
+			desc: "record type CNAME",
+			record: DNSRecord{
+				ID:           12345,
+				Hostname:     "asdf",
+				RecordType:   "CNAME",
+				Priority:     "0",
+				Destination:  "randomtext",
+				DeleteRecord: false,
+				State:        "yes",
+			},
+			expectError: true,
+		},
+	}
+
+	for _, test := range testCases {
+		test := test
+		t.Run(test.desc, func(t *testing.T) {
+			t.Parallel()
+
+			idx, err := GetDNSRecordIdx(records, test.record)
+			if test.expectError {
+				assert.Error(t, err)
+				assert.Equal(t, -1, idx)
+			} else {
+				assert.NoError(t, err)
+				assert.Equal(t, records[idx], test.record)
+			}
+		})
+	}
+}
diff --git a/providers/dns/netcup/netcup.go b/providers/dns/netcup/netcup.go
new file mode 100644
index 00000000..e7cc4c6b
--- /dev/null
+++ b/providers/dns/netcup/netcup.go
@@ -0,0 +1,116 @@
+// Package netcup implements a DNS Provider for solving the DNS-01 challenge using the netcup DNS API.
+package netcup
+
+import (
+	"fmt"
+	"net/http"
+	"strings"
+	"time"
+
+	"github.com/xenolf/lego/acme"
+	"github.com/xenolf/lego/platform/config/env"
+)
+
+// DNSProvider is an implementation of the acme.ChallengeProvider interface
+type DNSProvider struct {
+	client *Client
+}
+
+// NewDNSProvider returns a DNSProvider instance configured for netcup.
+// Credentials must be passed in the environment variables: NETCUP_CUSTOMER_NUMBER,
+// NETCUP_API_KEY, NETCUP_API_PASSWORD
+func NewDNSProvider() (*DNSProvider, error) {
+	values, err := env.Get("NETCUP_CUSTOMER_NUMBER", "NETCUP_API_KEY", "NETCUP_API_PASSWORD")
+	if err != nil {
+		return nil, fmt.Errorf("netcup: %v", err)
+	}
+
+	return NewDNSProviderCredentials(values["NETCUP_CUSTOMER_NUMBER"], values["NETCUP_API_KEY"], values["NETCUP_API_PASSWORD"])
+}
+
+// NewDNSProviderCredentials uses the supplied credentials to return a
+// DNSProvider instance configured for netcup.
+func NewDNSProviderCredentials(customer, key, password string) (*DNSProvider, error) {
+	if customer == "" || key == "" || password == "" {
+		return nil, fmt.Errorf("netcup: netcup credentials missing")
+	}
+
+	httpClient := &http.Client{
+		Timeout: 10 * time.Second,
+	}
+
+	return &DNSProvider{
+		client: NewClient(httpClient, customer, key, password),
+	}, nil
+}
+
+// Present creates a TXT record to fulfill the dns-01 challenge
+func (d *DNSProvider) Present(domainName, token, keyAuth string) error {
+	fqdn, value, _ := acme.DNS01Record(domainName, keyAuth)
+
+	zone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
+	if err != nil {
+		return fmt.Errorf("netcup: failed to find DNSZone, %v", err)
+	}
+
+	sessionID, err := d.client.Login()
+	if err != nil {
+		return err
+	}
+
+	hostname := strings.Replace(fqdn, "."+zone, "", 1)
+	record := CreateTxtRecord(hostname, value)
+
+	err = d.client.UpdateDNSRecord(sessionID, acme.UnFqdn(zone), record)
+	if err != nil {
+		if errLogout := d.client.Logout(sessionID); errLogout != nil {
+			return fmt.Errorf("failed to add TXT-Record: %v; %v", err, errLogout)
+		}
+		return fmt.Errorf("failed to add TXT-Record: %v", err)
+	}
+
+	return d.client.Logout(sessionID)
+}
+
+// CleanUp removes the TXT record matching the specified parameters
+func (d *DNSProvider) CleanUp(domainname, token, keyAuth string) error {
+	fqdn, value, _ := acme.DNS01Record(domainname, keyAuth)
+
+	zone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
+	if err != nil {
+		return fmt.Errorf("failed to find DNSZone, %v", err)
+	}
+
+	sessionID, err := d.client.Login()
+	if err != nil {
+		return err
+	}
+
+	hostname := strings.Replace(fqdn, "."+zone, "", 1)
+
+	zone = acme.UnFqdn(zone)
+
+	records, err := d.client.GetDNSRecords(zone, sessionID)
+	if err != nil {
+		return err
+	}
+
+	record := CreateTxtRecord(hostname, value)
+
+	idx, err := GetDNSRecordIdx(records, record)
+	if err != nil {
+		return err
+	}
+
+	records[idx].DeleteRecord = true
+
+	err = d.client.UpdateDNSRecord(sessionID, zone, records[idx])
+	if err != nil {
+		if errLogout := d.client.Logout(sessionID); errLogout != nil {
+			return fmt.Errorf("%v; %v", err, errLogout)
+		}
+		return err
+	}
+
+	return d.client.Logout(sessionID)
+}
diff --git a/providers/dns/netcup/netcup_test.go b/providers/dns/netcup/netcup_test.go
new file mode 100644
index 00000000..1c8c1b8a
--- /dev/null
+++ b/providers/dns/netcup/netcup_test.go
@@ -0,0 +1,62 @@
+package netcup
+
+import (
+	"fmt"
+	"os"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/xenolf/lego/acme"
+)
+
+var (
+	testLive           bool
+	testCustomerNumber string
+	testAPIKey         string
+	testAPIPassword    string
+	testDomain         string
+)
+
+func init() {
+	testCustomerNumber = os.Getenv("NETCUP_CUSTOMER_NUMBER")
+	testAPIKey = os.Getenv("NETCUP_API_KEY")
+	testAPIPassword = os.Getenv("NETCUP_API_PASSWORD")
+	testDomain = os.Getenv("NETCUP_DOMAIN")
+
+	if len(testCustomerNumber) > 0 && len(testAPIKey) > 0 && len(testAPIPassword) > 0 && len(testDomain) > 0 {
+		testLive = true
+	}
+}
+
+func TestDNSProviderPresentAndCleanup(t *testing.T) {
+	if !testLive {
+		t.Skip("skipping live test")
+	}
+
+	p, err := NewDNSProvider()
+	assert.NoError(t, err)
+
+	fqdn, _, _ := acme.DNS01Record(testDomain, "123d==")
+
+	zone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
+	assert.NoError(t, err, "error finding DNSZone")
+
+	zone = acme.UnFqdn(zone)
+
+	testCases := []string{
+		zone,
+		"sub." + zone,
+		"*." + zone,
+		"*.sub." + zone,
+	}
+
+	for _, tc := range testCases {
+		t.Run(fmt.Sprintf("domain(%s)", tc), func(t *testing.T) {
+			err = p.Present(tc, "987d", "123d==")
+			assert.NoError(t, err)
+
+			err = p.CleanUp(tc, "987d", "123d==")
+			assert.NoError(t, err, "Did not clean up! Please remove record yourself.")
+		})
+	}
+}