diff --git a/.dockerignore b/.dockerignore
index 057a20e..e3acf99 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -3,6 +3,7 @@
 // development
 integration_test.go
 integration_test/
+!integration_test/etc_embedded_derp/tls/server.crt
 
 Dockerfile*
 docker-compose*
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8535b45..6ce0c35 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,7 @@
 - Users can now use emails in ACL's groups [#372](https://github.com/juanfont/headscale/issues/372)
 - Add shorthand aliases for commands and subcommands [#376](https://github.com/juanfont/headscale/pull/376)
 - Add `/windows` endpoint for Windows configuration instructions + registry file download [#392](https://github.com/juanfont/headscale/pull/392)
+- Added embedded DERP server into Headscale [#388](https://github.com/juanfont/headscale/pull/388)
 
 ### Changes
 
diff --git a/Dockerfile.tailscale b/Dockerfile.tailscale
index f96c6b9..32a8ce7 100644
--- a/Dockerfile.tailscale
+++ b/Dockerfile.tailscale
@@ -7,5 +7,10 @@ RUN apt-get update \
     && curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/focal.gpg | apt-key add - \
     && curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/focal.list | tee /etc/apt/sources.list.d/tailscale.list \
     && apt-get update \
-    && apt-get install -y tailscale=${TAILSCALE_VERSION} dnsutils \
+    && apt-get install -y ca-certificates tailscale=${TAILSCALE_VERSION} dnsutils \
     && rm -rf /var/lib/apt/lists/*
+
+ADD integration_test/etc_embedded_derp/tls/server.crt /usr/local/share/ca-certificates/
+RUN chmod 644 /usr/local/share/ca-certificates/server.crt 
+
+RUN update-ca-certificates
diff --git a/Makefile b/Makefile
index 266dadb..73630d3 100644
--- a/Makefile
+++ b/Makefile
@@ -23,6 +23,9 @@ test_integration:
 test_integration_cli:
 	go test -tags integration -v integration_cli_test.go integration_common_test.go
 
+test_integration_derp:
+	go test -tags integration -v integration_embedded_derp_test.go integration_common_test.go
+
 coverprofile_func:
 	go tool cover -func=coverage.out
 
diff --git a/README.md b/README.md
index 1b97b1a..ec66b08 100644
--- a/README.md
+++ b/README.md
@@ -63,6 +63,7 @@ one of the maintainers.
 - Dual stack (IPv4 and IPv6)
 - Routing advertising (including exit nodes)
 - Ephemeral nodes
+- Embedded [DERP server](https://tailscale.com/blog/how-tailscale-works/#encrypted-tcp-relays-derp)
 
 ## Client OS support
 
diff --git a/app.go b/app.go
index 682d8e4..f1426bb 100644
--- a/app.go
+++ b/app.go
@@ -120,10 +120,16 @@ type OIDCConfig struct {
 }
 
 type DERPConfig struct {
-	URLs            []url.URL
-	Paths           []string
-	AutoUpdate      bool
-	UpdateFrequency time.Duration
+	ServerEnabled    bool
+	ServerRegionID   int
+	ServerRegionCode string
+	ServerRegionName string
+	STUNEnabled      bool
+	STUNAddr         string
+	URLs             []url.URL
+	Paths            []string
+	AutoUpdate       bool
+	UpdateFrequency  time.Duration
 }
 
 type CLIConfig struct {
@@ -142,7 +148,8 @@ type Headscale struct {
 	dbDebug    bool
 	privateKey *key.MachinePrivate
 
-	DERPMap *tailcfg.DERPMap
+	DERPMap    *tailcfg.DERPMap
+	DERPServer *DERPServer
 
 	aclPolicy *ACLPolicy
 	aclRules  []tailcfg.FilterRule
@@ -178,7 +185,6 @@ func LookupTLSClientAuthMode(mode string) (tls.ClientAuthType, bool) {
 	}
 }
 
-// NewHeadscale returns the Headscale app.
 func NewHeadscale(cfg Config) (*Headscale, error) {
 	privKey, err := readOrCreatePrivateKey(cfg.PrivateKeyPath)
 	if err != nil {
@@ -239,6 +245,14 @@ func NewHeadscale(cfg Config) (*Headscale, error) {
 		}
 	}
 
+	if cfg.DERP.ServerEnabled {
+		embeddedDERPServer, err := app.NewDERPServer()
+		if err != nil {
+			return nil, err
+		}
+		app.DERPServer = embeddedDERPServer
+	}
+
 	return &app, nil
 }
 
@@ -463,6 +477,12 @@ func (h *Headscale) createRouter(grpcMux *runtime.ServeMux) *gin.Engine {
 	router.GET("/swagger", SwaggerUI)
 	router.GET("/swagger/v1/openapiv2.json", SwaggerAPIv1)
 
+	if h.cfg.DERP.ServerEnabled {
+		router.Any("/derp", h.DERPHandler)
+		router.Any("/derp/probe", h.DERPProbeHandler)
+		router.Any("/bootstrap-dns", h.DERPBootstrapDNSHandler)
+	}
+
 	api := router.Group("/api")
 	api.Use(h.httpAuthenticationMiddleware)
 	{
@@ -481,6 +501,13 @@ func (h *Headscale) Serve() error {
 	// Fetch an initial DERP Map before we start serving
 	h.DERPMap = GetDERPMap(h.cfg.DERP)
 
+	if h.cfg.DERP.ServerEnabled {
+		h.DERPMap.Regions[h.DERPServer.region.RegionID] = &h.DERPServer.region
+		if h.cfg.DERP.STUNEnabled {
+			go h.ServeSTUN()
+		}
+	}
+
 	if h.cfg.DERP.AutoUpdate {
 		derpMapCancelChannel := make(chan struct{})
 		defer func() { derpMapCancelChannel <- struct{}{} }()
diff --git a/cmd/headscale/cli/server.go b/cmd/headscale/cli/server.go
index 6d9ad19..c19580b 100644
--- a/cmd/headscale/cli/server.go
+++ b/cmd/headscale/cli/server.go
@@ -1,8 +1,7 @@
 package cli
 
 import (
-	"log"
-
+	"github.com/rs/zerolog/log"
 	"github.com/spf13/cobra"
 )
 
@@ -19,12 +18,12 @@ var serveCmd = &cobra.Command{
 	Run: func(cmd *cobra.Command, args []string) {
 		h, err := getHeadscaleApp()
 		if err != nil {
-			log.Fatalf("Error initializing: %s", err)
+			log.Fatal().Caller().Err(err).Msg("Error initializing")
 		}
 
 		err = h.Serve()
 		if err != nil {
-			log.Fatalf("Error initializing: %s", err)
+			log.Fatal().Caller().Err(err).Msg("Error starting server")
 		}
 	},
 }
diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go
index 49fc23c..dc7a4e9 100644
--- a/cmd/headscale/cli/utils.go
+++ b/cmd/headscale/cli/utils.go
@@ -117,6 +117,13 @@ func LoadConfig(path string) error {
 }
 
 func GetDERPConfig() headscale.DERPConfig {
+	serverEnabled := viper.GetBool("derp.server.enabled")
+	serverRegionID := viper.GetInt("derp.server.region_id")
+	serverRegionCode := viper.GetString("derp.server.region_code")
+	serverRegionName := viper.GetString("derp.server.region_name")
+	stunEnabled := viper.GetBool("derp.server.stun.enabled")
+	stunAddr := viper.GetString("derp.server.stun.listen_addr")
+
 	urlStrs := viper.GetStringSlice("derp.urls")
 
 	urls := make([]url.URL, len(urlStrs))
@@ -138,10 +145,16 @@ func GetDERPConfig() headscale.DERPConfig {
 	updateFrequency := viper.GetDuration("derp.update_frequency")
 
 	return headscale.DERPConfig{
-		URLs:            urls,
-		Paths:           paths,
-		AutoUpdate:      autoUpdate,
-		UpdateFrequency: updateFrequency,
+		ServerEnabled:    serverEnabled,
+		ServerRegionID:   serverRegionID,
+		ServerRegionCode: serverRegionCode,
+		ServerRegionName: serverRegionName,
+		STUNEnabled:      stunEnabled,
+		STUNAddr:         stunAddr,
+		URLs:             urls,
+		Paths:            paths,
+		AutoUpdate:       autoUpdate,
+		UpdateFrequency:  updateFrequency,
 	}
 }
 
diff --git a/config-example.yaml b/config-example.yaml
index a8c3f28..2075e69 100644
--- a/config-example.yaml
+++ b/config-example.yaml
@@ -55,6 +55,26 @@ ip_prefixes:
 # headscale needs a list of DERP servers that can be presented
 # to the clients.
 derp:
+  server:
+    # If enabled, runs the embedded DERP server and merges it into the rest of the DERP config
+    # The Headscale server_url defined above MUST be using https, DERP requires TLS to be in place
+    enabled: false
+
+    # Region ID to use for the embedded DERP server.
+    # The local DERP prevails if the region ID collides with other region ID coming from
+    # the regular DERP config.
+    region_id: 999
+
+    # Region code and name are displayed in the Tailscale UI to identify a DERP region
+    region_code: "headscale"
+    region_name: "Headscale Embedded DERP"
+
+    # If enabled, also listens in UDP at the configured address for STUN connections to help on NAT traversal
+    # For more details on how this works, check this great article: https://tailscale.com/blog/how-tailscale-works/
+    stun:
+      enabled: false
+      listen_addr: "0.0.0.0:3478"
+
   # List of externally available DERP maps encoded in JSON
   urls:
     - https://controlplane.tailscale.com/derpmap/default
diff --git a/derp.go b/derp.go
index 63e448d..7a9b236 100644
--- a/derp.go
+++ b/derp.go
@@ -148,6 +148,7 @@ func (h *Headscale) scheduledDERPMapUpdateWorker(cancelChan <-chan struct{}) {
 		case <-ticker.C:
 			log.Info().Msg("Fetching DERPMap updates")
 			h.DERPMap = GetDERPMap(h.cfg.DERP)
+			h.DERPMap.Regions[h.DERPServer.region.RegionID] = &h.DERPServer.region
 
 			namespaces, err := h.ListNamespaces()
 			if err != nil {
diff --git a/derp_server.go b/derp_server.go
new file mode 100644
index 0000000..11e3eb1
--- /dev/null
+++ b/derp_server.go
@@ -0,0 +1,233 @@
+package headscale
+
+import (
+	"context"
+	"fmt"
+	"net"
+	"net/http"
+	"net/url"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/gin-gonic/gin"
+	"github.com/rs/zerolog/log"
+	"tailscale.com/derp"
+	"tailscale.com/net/stun"
+	"tailscale.com/tailcfg"
+	"tailscale.com/types/key"
+)
+
+// fastStartHeader is the header (with value "1") that signals to the HTTP
+// server that the DERP HTTP client does not want the HTTP 101 response
+// headers and it will begin writing & reading the DERP protocol immediately
+// following its HTTP request.
+const fastStartHeader = "Derp-Fast-Start"
+
+type DERPServer struct {
+	tailscaleDERP *derp.Server
+	region        tailcfg.DERPRegion
+}
+
+func (h *Headscale) NewDERPServer() (*DERPServer, error) {
+	server := derp.NewServer(key.NodePrivate(*h.privateKey), log.Info().Msgf)
+	region, err := h.generateRegionLocalDERP()
+	if err != nil {
+		return nil, err
+	}
+
+	return &DERPServer{server, region}, nil
+}
+
+func (h *Headscale) generateRegionLocalDERP() (tailcfg.DERPRegion, error) {
+	serverURL, err := url.Parse(h.cfg.ServerURL)
+	if err != nil {
+		return tailcfg.DERPRegion{}, err
+	}
+	var host string
+	var port int
+	host, portStr, err := net.SplitHostPort(serverURL.Host)
+	if err != nil {
+		if serverURL.Scheme == "https" {
+			host = serverURL.Host
+			port = 443
+		} else {
+			host = serverURL.Host
+			port = 80
+		}
+	} else {
+		port, err = strconv.Atoi(portStr)
+		if err != nil {
+			return tailcfg.DERPRegion{}, err
+		}
+	}
+
+	localDERPregion := tailcfg.DERPRegion{
+		RegionID:   h.cfg.DERP.ServerRegionID,
+		RegionCode: h.cfg.DERP.ServerRegionCode,
+		RegionName: h.cfg.DERP.ServerRegionName,
+		Avoid:      false,
+		Nodes: []*tailcfg.DERPNode{
+			{
+				Name:     fmt.Sprintf("%d", h.cfg.DERP.ServerRegionID),
+				RegionID: h.cfg.DERP.ServerRegionID,
+				HostName: host,
+				DERPPort: port,
+			},
+		},
+	}
+
+	if h.cfg.DERP.STUNEnabled {
+		_, portStr, err := net.SplitHostPort(h.cfg.DERP.STUNAddr)
+		if err != nil {
+			return tailcfg.DERPRegion{}, err
+		}
+		port, err := strconv.Atoi(portStr)
+		if err != nil {
+			return tailcfg.DERPRegion{}, err
+		}
+		localDERPregion.Nodes[0].STUNPort = port
+	}
+
+	return localDERPregion, nil
+}
+
+func (h *Headscale) DERPHandler(ctx *gin.Context) {
+	log.Trace().Caller().Msgf("/derp request from %v", ctx.ClientIP())
+	up := strings.ToLower(ctx.Request.Header.Get("Upgrade"))
+	if up != "websocket" && up != "derp" {
+		if up != "" {
+			log.Warn().Caller().Msgf("Weird websockets connection upgrade: %q", up)
+		}
+		ctx.String(http.StatusUpgradeRequired, "DERP requires connection upgrade")
+
+		return
+	}
+
+	fastStart := ctx.Request.Header.Get(fastStartHeader) == "1"
+
+	hijacker, ok := ctx.Writer.(http.Hijacker)
+	if !ok {
+		log.Error().Caller().Msg("DERP requires Hijacker interface from Gin")
+		ctx.String(http.StatusInternalServerError, "HTTP does not support general TCP support")
+
+		return
+	}
+
+	netConn, conn, err := hijacker.Hijack()
+	if err != nil {
+		log.Error().Caller().Err(err).Msgf("Hijack failed")
+		ctx.String(http.StatusInternalServerError, "HTTP does not support general TCP support")
+
+		return
+	}
+
+	if !fastStart {
+		pubKey := h.privateKey.Public()
+		pubKeyStr := pubKey.UntypedHexString() // nolint
+		fmt.Fprintf(conn, "HTTP/1.1 101 Switching Protocols\r\n"+
+			"Upgrade: DERP\r\n"+
+			"Connection: Upgrade\r\n"+
+			"Derp-Version: %v\r\n"+
+			"Derp-Public-Key: %s\r\n\r\n",
+			derp.ProtocolVersion,
+			pubKeyStr)
+	}
+
+	h.DERPServer.tailscaleDERP.Accept(netConn, conn, netConn.RemoteAddr().String())
+}
+
+// DERPProbeHandler is the endpoint that js/wasm clients hit to measure
+// DERP latency, since they can't do UDP STUN queries.
+func (h *Headscale) DERPProbeHandler(ctx *gin.Context) {
+	switch ctx.Request.Method {
+	case "HEAD", "GET":
+		ctx.Writer.Header().Set("Access-Control-Allow-Origin", "*")
+	default:
+		ctx.String(http.StatusMethodNotAllowed, "bogus probe method")
+	}
+}
+
+// DERPBootstrapDNSHandler implements the /bootsrap-dns endpoint
+// Described in https://github.com/tailscale/tailscale/issues/1405,
+// this endpoint provides a way to help a client when it fails to start up
+// because its DNS are broken.
+// The initial implementation is here https://github.com/tailscale/tailscale/pull/1406
+// They have a cache, but not clear if that is really necessary at Headscale, uh, scale.
+// An example implementation is found here https://derp.tailscale.com/bootstrap-dns
+func (h *Headscale) DERPBootstrapDNSHandler(ctx *gin.Context) {
+	dnsEntries := make(map[string][]net.IP)
+
+	resolvCtx, cancel := context.WithTimeout(context.Background(), time.Minute)
+	defer cancel()
+	var r net.Resolver
+	for _, region := range h.DERPMap.Regions {
+		for _, node := range region.Nodes { // we don't care if we override some nodes
+			addrs, err := r.LookupIP(resolvCtx, "ip", node.HostName)
+			if err != nil {
+				log.Trace().Caller().Err(err).Msgf("bootstrap DNS lookup failed %q", node.HostName)
+
+				continue
+			}
+			dnsEntries[node.HostName] = addrs
+		}
+	}
+	ctx.JSON(http.StatusOK, dnsEntries)
+}
+
+// ServeSTUN starts a STUN server on the configured addr.
+func (h *Headscale) ServeSTUN() {
+	packetConn, err := net.ListenPacket("udp", h.cfg.DERP.STUNAddr)
+	if err != nil {
+		log.Fatal().Msgf("failed to open STUN listener: %v", err)
+	}
+	log.Info().Msgf("STUN server started at %s", packetConn.LocalAddr())
+
+	udpConn, ok := packetConn.(*net.UDPConn)
+	if !ok {
+		log.Fatal().Msg("STUN listener is not a UDP listener")
+	}
+	serverSTUNListener(context.Background(), udpConn)
+}
+
+func serverSTUNListener(ctx context.Context, packetConn *net.UDPConn) {
+	var buf [64 << 10]byte
+	var (
+		bytesRead int
+		udpAddr   *net.UDPAddr
+		err       error
+	)
+	for {
+		bytesRead, udpAddr, err = packetConn.ReadFromUDP(buf[:])
+		if err != nil {
+			if ctx.Err() != nil {
+				return
+			}
+			log.Error().Caller().Err(err).Msgf("STUN ReadFrom")
+			time.Sleep(time.Second)
+
+			continue
+		}
+		log.Trace().Caller().Msgf("STUN request from %v", udpAddr)
+		pkt := buf[:bytesRead]
+		if !stun.Is(pkt) {
+			log.Trace().Caller().Msgf("UDP packet is not STUN")
+
+			continue
+		}
+		txid, err := stun.ParseBindingRequest(pkt)
+		if err != nil {
+			log.Trace().Caller().Err(err).Msgf("STUN parse error")
+
+			continue
+		}
+
+		res := stun.Response(txid, udpAddr.IP, uint16(udpAddr.Port))
+		_, err = packetConn.WriteTo(res, udpAddr)
+		if err != nil {
+			log.Trace().Caller().Err(err).Msgf("Issue writing to UDP")
+
+			continue
+		}
+	}
+}
diff --git a/go.mod b/go.mod
index a121826..1ec291c 100644
--- a/go.mod
+++ b/go.mod
@@ -46,8 +46,10 @@ require (
 	github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
 	github.com/Microsoft/go-winio v0.5.2 // indirect
 	github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
+	github.com/akutz/memconn v0.1.0 // indirect
 	github.com/atomicgo/cursor v0.0.1 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
+	github.com/ccding/go-stun/stun v0.0.0-20200514191101-4dc67bcdb029 // indirect
 	github.com/cenkalti/backoff/v4 v4.1.2 // indirect
 	github.com/cespare/xxhash/v2 v2.1.2 // indirect
 	github.com/containerd/continuity v0.2.2 // indirect
@@ -100,6 +102,7 @@ require (
 	github.com/mattn/go-sqlite3 v1.14.11 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
 	github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
+	github.com/mitchellh/go-ps v1.0.0 // indirect
 	github.com/mitchellh/mapstructure v1.4.3 // indirect
 	github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
@@ -134,6 +137,7 @@ require (
 	golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect
 	golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
 	golang.org/x/text v0.3.7 // indirect
+	golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
 	gopkg.in/ini.v1 v1.66.4 // indirect
 	gopkg.in/square/go-jose.v2 v2.6.0 // indirect
diff --git a/go.sum b/go.sum
index 558d3d7..6d254a2 100644
--- a/go.sum
+++ b/go.sum
@@ -82,6 +82,8 @@ github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEV
 github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
+github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A=
+github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@@ -106,6 +108,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
 github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
 github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
 github.com/bufbuild/buf v0.37.0/go.mod h1:lQ1m2HkIaGOFba6w/aC3KYBHhKEOESP3gaAEpS3dAFM=
+github.com/ccding/go-stun/stun v0.0.0-20200514191101-4dc67bcdb029 h1:POmUHfxXdeyM8Aomg4tKDcwATCFuW+cYLkj6pwsw9pc=
+github.com/ccding/go-stun/stun v0.0.0-20200514191101-4dc67bcdb029/go.mod h1:Rpr5n9cGHYdM3S3IK8ROSUUUYjQOu+MSUCZDcJbYWi8=
 github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo=
 github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -205,6 +209,7 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF
 github.com/fatih/set v0.2.1 h1:nn2CaJyknWE/6txyUDGwysr3G5QC6xWB/PtVjPBbeaA=
 github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI=
 github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
+github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
 github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
@@ -570,6 +575,8 @@ github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceT
 github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
 github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
+github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
 github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
 github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
@@ -1083,6 +1090,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 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-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M=
+golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/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/integration_common_test.go b/integration_common_test.go
index de304d0..70285fc 100644
--- a/integration_common_test.go
+++ b/integration_common_test.go
@@ -6,6 +6,7 @@ package headscale
 import (
 	"bytes"
 	"fmt"
+	"strings"
 	"time"
 
 	"github.com/ory/dockertest/v3"
@@ -18,8 +19,15 @@ const DOCKER_EXECUTE_TIMEOUT = 10 * time.Second
 var (
 	IpPrefix4 = netaddr.MustParseIPPrefix("100.64.0.0/10")
 	IpPrefix6 = netaddr.MustParseIPPrefix("fd7a:115c:a1e0::/48")
+
+	tailscaleVersions = []string{"1.22.0", "1.20.4", "1.18.2", "1.16.2", "1.14.3", "1.12.3"}
 )
 
+type TestNamespace struct {
+	count      int
+	tailscales map[string]dockertest.Resource
+}
+
 type ExecuteCommandConfig struct {
 	timeout time.Duration
 }
@@ -119,3 +127,35 @@ func DockerAllowNetworkAdministration(config *docker.HostConfig) {
 		Target: "/dev/net/tun",
 	})
 }
+
+func getIPs(
+	tailscales map[string]dockertest.Resource,
+) (map[string][]netaddr.IP, error) {
+	ips := make(map[string][]netaddr.IP)
+	for hostname, tailscale := range tailscales {
+		command := []string{"tailscale", "ip"}
+
+		result, err := ExecuteCommand(
+			&tailscale,
+			command,
+			[]string{},
+		)
+		if err != nil {
+			return nil, err
+		}
+
+		for _, address := range strings.Split(result, "\n") {
+			address = strings.TrimSuffix(address, "\n")
+			if len(address) < 1 {
+				continue
+			}
+			ip, err := netaddr.ParseIP(address)
+			if err != nil {
+				return nil, err
+			}
+			ips[hostname] = append(ips[hostname], ip)
+		}
+	}
+
+	return ips, nil
+}
diff --git a/integration_embedded_derp_test.go b/integration_embedded_derp_test.go
new file mode 100644
index 0000000..a173717
--- /dev/null
+++ b/integration_embedded_derp_test.go
@@ -0,0 +1,396 @@
+//go:build integration
+
+package headscale
+
+import (
+	"bytes"
+	"context"
+	"crypto/tls"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"os"
+	"path"
+	"strings"
+	"sync"
+	"testing"
+	"time"
+
+	v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
+	"github.com/ory/dockertest/v3"
+	"github.com/ory/dockertest/v3/docker"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/suite"
+
+	"github.com/ccding/go-stun/stun"
+)
+
+const (
+	headscaleHostname = "headscale-derp"
+	namespaceName     = "derpnamespace"
+	totalContainers   = 3
+)
+
+type IntegrationDERPTestSuite struct {
+	suite.Suite
+	stats *suite.SuiteInformation
+
+	pool      dockertest.Pool
+	networks  map[int]dockertest.Network // so we keep the containers isolated
+	headscale dockertest.Resource
+
+	tailscales    map[string]dockertest.Resource
+	joinWaitGroup sync.WaitGroup
+}
+
+func TestDERPIntegrationTestSuite(t *testing.T) {
+	s := new(IntegrationDERPTestSuite)
+
+	s.tailscales = make(map[string]dockertest.Resource)
+	s.networks = make(map[int]dockertest.Network)
+
+	suite.Run(t, s)
+
+	// HandleStats, which allows us to check if we passed and save logs
+	// is called after TearDown, so we cannot tear down containers before
+	// we have potentially saved the logs.
+	for _, tailscale := range s.tailscales {
+		if err := s.pool.Purge(&tailscale); err != nil {
+			log.Printf("Could not purge resource: %s\n", err)
+		}
+	}
+
+	if !s.stats.Passed() {
+		err := s.saveLog(&s.headscale, "test_output")
+		if err != nil {
+			log.Printf("Could not save log: %s\n", err)
+		}
+	}
+	if err := s.pool.Purge(&s.headscale); err != nil {
+		log.Printf("Could not purge resource: %s\n", err)
+	}
+
+	for _, network := range s.networks {
+		if err := network.Close(); err != nil {
+			log.Printf("Could not close network: %s\n", err)
+		}
+	}
+}
+
+func (s *IntegrationDERPTestSuite) SetupSuite() {
+	if ppool, err := dockertest.NewPool(""); err == nil {
+		s.pool = *ppool
+	} else {
+		log.Fatalf("Could not connect to docker: %s", err)
+	}
+
+	for i := 0; i < totalContainers; i++ {
+		if pnetwork, err := s.pool.CreateNetwork(fmt.Sprintf("headscale-derp-%d", i)); err == nil {
+			s.networks[i] = *pnetwork
+		} else {
+			log.Fatalf("Could not create network: %s", err)
+		}
+	}
+
+	headscaleBuildOptions := &dockertest.BuildOptions{
+		Dockerfile: "Dockerfile",
+		ContextDir: ".",
+	}
+
+	currentPath, err := os.Getwd()
+	if err != nil {
+		log.Fatalf("Could not determine current path: %s", err)
+	}
+
+	headscaleOptions := &dockertest.RunOptions{
+		Name: headscaleHostname,
+		Mounts: []string{
+			fmt.Sprintf("%s/integration_test/etc_embedded_derp:/etc/headscale", currentPath),
+		},
+		Cmd:          []string{"headscale", "serve"},
+		ExposedPorts: []string{"8443/tcp", "3478/udp"},
+		PortBindings: map[docker.Port][]docker.PortBinding{
+			"8443/tcp": {{HostPort: "8443"}},
+			"3478/udp": {{HostPort: "3478"}},
+		},
+	}
+
+	log.Println("Creating headscale container")
+	if pheadscale, err := s.pool.BuildAndRunWithBuildOptions(headscaleBuildOptions, headscaleOptions, DockerRestartPolicy); err == nil {
+		s.headscale = *pheadscale
+	} else {
+		log.Fatalf("Could not start resource: %s", err)
+	}
+	log.Println("Created headscale container to test DERP")
+
+	log.Println("Creating tailscale containers")
+
+	for i := 0; i < totalContainers; i++ {
+		version := tailscaleVersions[i%len(tailscaleVersions)]
+		hostname, container := s.tailscaleContainer(
+			fmt.Sprint(i),
+			version,
+			s.networks[i],
+		)
+		s.tailscales[hostname] = *container
+	}
+
+	log.Println("Waiting for headscale to be ready")
+	hostEndpoint := fmt.Sprintf("localhost:%s", s.headscale.GetPort("8443/tcp"))
+
+	if err := s.pool.Retry(func() error {
+		url := fmt.Sprintf("https://%s/health", hostEndpoint)
+		insecureTransport := http.DefaultTransport.(*http.Transport).Clone()
+		insecureTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
+		client := &http.Client{Transport: insecureTransport}
+		resp, err := client.Get(url)
+		if err != nil {
+			return err
+		}
+
+		if resp.StatusCode != http.StatusOK {
+			return fmt.Errorf("status code not OK")
+		}
+
+		return nil
+	}); err != nil {
+		// TODO(kradalby): If we cannot access headscale, or any other fatal error during
+		// test setup, we need to abort and tear down. However, testify does not seem to
+		// support that at the moment:
+		// https://github.com/stretchr/testify/issues/849
+		return // fmt.Errorf("Could not connect to headscale: %s", err)
+	}
+	log.Println("headscale container is ready")
+
+	log.Printf("Creating headscale namespace: %s\n", namespaceName)
+	result, err := ExecuteCommand(
+		&s.headscale,
+		[]string{"headscale", "namespaces", "create", namespaceName},
+		[]string{},
+	)
+	log.Println("headscale create namespace result: ", result)
+	assert.Nil(s.T(), err)
+
+	log.Printf("Creating pre auth key for %s\n", namespaceName)
+	preAuthResult, err := ExecuteCommand(
+		&s.headscale,
+		[]string{
+			"headscale",
+			"--namespace",
+			namespaceName,
+			"preauthkeys",
+			"create",
+			"--reusable",
+			"--expiration",
+			"24h",
+			"--output",
+			"json",
+		},
+		[]string{"LOG_LEVEL=error"},
+	)
+	assert.Nil(s.T(), err)
+
+	var preAuthKey v1.PreAuthKey
+	err = json.Unmarshal([]byte(preAuthResult), &preAuthKey)
+	assert.Nil(s.T(), err)
+	assert.True(s.T(), preAuthKey.Reusable)
+
+	headscaleEndpoint := fmt.Sprintf("https://headscale:%s", s.headscale.GetPort("8443/tcp"))
+
+	log.Printf(
+		"Joining tailscale containers to headscale at %s\n",
+		headscaleEndpoint,
+	)
+	for hostname, tailscale := range s.tailscales {
+		s.joinWaitGroup.Add(1)
+		go s.Join(headscaleEndpoint, preAuthKey.Key, hostname, tailscale)
+	}
+
+	s.joinWaitGroup.Wait()
+
+	// The nodes need a bit of time to get their updated maps from headscale
+	// TODO: See if we can have a more deterministic wait here.
+	time.Sleep(60 * time.Second)
+}
+
+func (s *IntegrationDERPTestSuite) Join(
+	endpoint, key, hostname string,
+	tailscale dockertest.Resource,
+) {
+	defer s.joinWaitGroup.Done()
+
+	command := []string{
+		"tailscale",
+		"up",
+		"-login-server",
+		endpoint,
+		"--authkey",
+		key,
+		"--hostname",
+		hostname,
+	}
+
+	log.Println("Join command:", command)
+	log.Printf("Running join command for %s\n", hostname)
+	_, err := ExecuteCommand(
+		&tailscale,
+		command,
+		[]string{},
+	)
+	assert.Nil(s.T(), err)
+	log.Printf("%s joined\n", hostname)
+}
+
+func (s *IntegrationDERPTestSuite) tailscaleContainer(identifier, version string, network dockertest.Network,
+) (string, *dockertest.Resource) {
+	tailscaleBuildOptions := &dockertest.BuildOptions{
+		Dockerfile: "Dockerfile.tailscale",
+		ContextDir: ".",
+		BuildArgs: []docker.BuildArg{
+			{
+				Name:  "TAILSCALE_VERSION",
+				Value: version,
+			},
+		},
+	}
+	hostname := fmt.Sprintf(
+		"tailscale-%s-%s",
+		strings.Replace(version, ".", "-", -1),
+		identifier,
+	)
+	tailscaleOptions := &dockertest.RunOptions{
+		Name:     hostname,
+		Networks: []*dockertest.Network{&network},
+		Cmd: []string{
+			"tailscaled", "--tun=tsdev",
+		},
+
+		// expose the host IP address, so we can access it from inside the container
+		ExtraHosts: []string{"host.docker.internal:host-gateway", "headscale:host-gateway"},
+	}
+
+	pts, err := s.pool.BuildAndRunWithBuildOptions(
+		tailscaleBuildOptions,
+		tailscaleOptions,
+		DockerRestartPolicy,
+		DockerAllowLocalIPv6,
+		DockerAllowNetworkAdministration,
+	)
+	if err != nil {
+		log.Fatalf("Could not start resource: %s", err)
+	}
+	log.Printf("Created %s container\n", hostname)
+
+	return hostname, pts
+}
+
+func (s *IntegrationDERPTestSuite) TearDownSuite() {
+}
+
+func (s *IntegrationDERPTestSuite) HandleStats(
+	suiteName string,
+	stats *suite.SuiteInformation,
+) {
+	s.stats = stats
+}
+
+func (s *IntegrationDERPTestSuite) saveLog(
+	resource *dockertest.Resource,
+	basePath string,
+) error {
+	err := os.MkdirAll(basePath, os.ModePerm)
+	if err != nil {
+		return err
+	}
+
+	var stdout bytes.Buffer
+	var stderr bytes.Buffer
+
+	err = s.pool.Client.Logs(
+		docker.LogsOptions{
+			Context:      context.TODO(),
+			Container:    resource.Container.ID,
+			OutputStream: &stdout,
+			ErrorStream:  &stderr,
+			Tail:         "all",
+			RawTerminal:  false,
+			Stdout:       true,
+			Stderr:       true,
+			Follow:       false,
+			Timestamps:   false,
+		},
+	)
+	if err != nil {
+		return err
+	}
+
+	log.Printf("Saving logs for %s to %s\n", resource.Container.Name, basePath)
+
+	err = ioutil.WriteFile(
+		path.Join(basePath, resource.Container.Name+".stdout.log"),
+		[]byte(stdout.String()),
+		0o644,
+	)
+	if err != nil {
+		return err
+	}
+
+	err = ioutil.WriteFile(
+		path.Join(basePath, resource.Container.Name+".stderr.log"),
+		[]byte(stdout.String()),
+		0o644,
+	)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (s *IntegrationDERPTestSuite) TestPingAllPeersByHostname() {
+	ips, err := getIPs(s.tailscales)
+	assert.Nil(s.T(), err)
+	for hostname, tailscale := range s.tailscales {
+		for peername := range ips {
+			if peername == hostname {
+				continue
+			}
+			s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) {
+				command := []string{
+					"tailscale", "ping",
+					"--timeout=10s",
+					"--c=5",
+					"--until-direct=false",
+					peername,
+				}
+
+				log.Printf(
+					"Pinging using hostname from %s to %s\n",
+					hostname,
+					peername,
+				)
+				log.Println(command)
+				result, err := ExecuteCommand(
+					&tailscale,
+					command,
+					[]string{},
+				)
+				assert.Nil(t, err)
+				log.Printf("Result for %s: %s\n", hostname, result)
+				assert.Contains(t, result, "via DERP(headscale)")
+			})
+		}
+	}
+}
+
+func (s *IntegrationDERPTestSuite) TestDERPSTUN() {
+	headscaleSTUNAddr := fmt.Sprintf("localhost:%s", s.headscale.GetPort("3478/udp"))
+	client := stun.NewClient()
+	client.SetVerbose(true)
+	client.SetVVerbose(true)
+	client.SetServerAddr(headscaleSTUNAddr)
+	_, _, err := client.Discover()
+	assert.Nil(s.T(), err)
+}
diff --git a/integration_test.go b/integration_test.go
index 03d6d2f..1649f32 100644
--- a/integration_test.go
+++ b/integration_test.go
@@ -29,13 +29,6 @@ import (
 	"tailscale.com/ipn/ipnstate"
 )
 
-var tailscaleVersions = []string{"1.20.4", "1.18.2", "1.16.2", "1.14.3", "1.12.3"}
-
-type TestNamespace struct {
-	count      int
-	tailscales map[string]dockertest.Resource
-}
-
 type IntegrationTestSuite struct {
 	suite.Suite
 	stats *suite.SuiteInformation
@@ -687,38 +680,6 @@ func (s *IntegrationTestSuite) TestMagicDNS() {
 	}
 }
 
-func getIPs(
-	tailscales map[string]dockertest.Resource,
-) (map[string][]netaddr.IP, error) {
-	ips := make(map[string][]netaddr.IP)
-	for hostname, tailscale := range tailscales {
-		command := []string{"tailscale", "ip"}
-
-		result, err := ExecuteCommand(
-			&tailscale,
-			command,
-			[]string{},
-		)
-		if err != nil {
-			return nil, err
-		}
-
-		for _, address := range strings.Split(result, "\n") {
-			address = strings.TrimSuffix(address, "\n")
-			if len(address) < 1 {
-				continue
-			}
-			ip, err := netaddr.ParseIP(address)
-			if err != nil {
-				return nil, err
-			}
-			ips[hostname] = append(ips[hostname], ip)
-		}
-	}
-
-	return ips, nil
-}
-
 func getAPIURLs(
 	tailscales map[string]dockertest.Resource,
 ) (map[netaddr.IP]string, error) {
diff --git a/integration_test/etc_embedded_derp/config.yaml b/integration_test/etc_embedded_derp/config.yaml
new file mode 100644
index 0000000..1531d34
--- /dev/null
+++ b/integration_test/etc_embedded_derp/config.yaml
@@ -0,0 +1,29 @@
+log_level: trace
+acl_policy_path: ""
+db_type: sqlite3
+ephemeral_node_inactivity_timeout: 30m
+ip_prefixes:
+  - fd7a:115c:a1e0::/48
+  - 100.64.0.0/10
+dns_config:
+  base_domain: headscale.net
+  magic_dns: true
+  domains: []
+  nameservers:
+    - 1.1.1.1
+db_path: /tmp/integration_test_db.sqlite3
+private_key_path: private.key
+listen_addr: 0.0.0.0:8443
+server_url: https://headscale:8443
+tls_cert_path: "/etc/headscale/tls/server.crt"
+tls_key_path: "/etc/headscale/tls/server.key"
+tls_client_auth_mode: disabled
+derp:
+  server:
+    enabled: true
+    region_id: 999
+    region_code: "headscale"
+    region_name: "Headscale Embedded DERP"
+    stun:
+      enabled: true
+      listen_addr: "0.0.0.0:3478"
diff --git a/integration_test/etc_embedded_derp/tls/server.crt b/integration_test/etc_embedded_derp/tls/server.crt
new file mode 100644
index 0000000..9555649
--- /dev/null
+++ b/integration_test/etc_embedded_derp/tls/server.crt
@@ -0,0 +1,22 @@
+
+-----BEGIN CERTIFICATE-----
+MIIC8jCCAdqgAwIBAgIULbu+UbSTMG/LtxooLLh7BgSEyqEwDQYJKoZIhvcNAQEL
+BQAwFDESMBAGA1UEAwwJaGVhZHNjYWxlMCAXDTIyMDMwNTE2NDgwM1oYDzI1MjEx
+MTA0MTY0ODAzWjAUMRIwEAYDVQQDDAloZWFkc2NhbGUwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQDqcfpToLZUF0rlNwXkkt3lbyw4Cl4TJdx36o2PKaOK
+U+tze/IjRsCWeMwrcR1o9TNZcxsD+c2J48D1WATuQJlMeg+2UJXGaTGRKkkbPMy3
+5m7AFf/Q16UEOgm2NYjZaQ8faRGIMYURG/6sXmNeETJvBixpBev9yKJuVXgqHNS4
+NpEkNwdOCuAZXrmw0HCbiusawJOay4tFvhH14rav8Uimonl8UTNVXufMzyUOuoaQ
+TGflmzYX3hIoswRnTPlIWFoqObvx2Q8H+of3uQJXy0m8I6OrIoXLNxnqYMfFls79
+9SYgVc2jPsCbh5fwyRbx2Hof7sIZ1K/mNgxJRG1E3ZiLAgMBAAGjOjA4MBQGA1Ud
+EQQNMAuCCWhlYWRzY2FsZTALBgNVHQ8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUH
+AwEwDQYJKoZIhvcNAQELBQADggEBANGlVN7NCsJaKz0k0nhlRGK+tcxn2p1PXN/i
+Iy+JX8ahixPC4ocRwOhrXgb390ZXLLwq08HrWYRB/Wi1VUzCp5d8dVxvrR43dJ+v
+L2EOBiIKgcu2C3pWW1qRR46/EoXUU9kSH2VNBvIhNufi32kEOidoDzxtQf6qVCoF
+guUt1JkAqrynv1UvR/2ZRM/WzM/oJ8qfECwrwDxyYhkqU5Z5jCWg0C6kPIBvNdzt
+B0eheWS+ZxVwkePTR4e17kIafwknth3lo+orxVrq/xC+OVM1bGrt2ZyD64ZvEqQl
+w6kgbzBdLScAQptWOFThwhnJsg0UbYKimZsnYmjVEuN59TJv92M=
+-----END CERTIFICATE-----
+
+(Expires on Nov  4 16:48:03 2521 GMT)
+
diff --git a/integration_test/etc_embedded_derp/tls/server.key b/integration_test/etc_embedded_derp/tls/server.key
new file mode 100644
index 0000000..8a2df34
--- /dev/null
+++ b/integration_test/etc_embedded_derp/tls/server.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDqcfpToLZUF0rl
+NwXkkt3lbyw4Cl4TJdx36o2PKaOKU+tze/IjRsCWeMwrcR1o9TNZcxsD+c2J48D1
+WATuQJlMeg+2UJXGaTGRKkkbPMy35m7AFf/Q16UEOgm2NYjZaQ8faRGIMYURG/6s
+XmNeETJvBixpBev9yKJuVXgqHNS4NpEkNwdOCuAZXrmw0HCbiusawJOay4tFvhH1
+4rav8Uimonl8UTNVXufMzyUOuoaQTGflmzYX3hIoswRnTPlIWFoqObvx2Q8H+of3
+uQJXy0m8I6OrIoXLNxnqYMfFls799SYgVc2jPsCbh5fwyRbx2Hof7sIZ1K/mNgxJ
+RG1E3ZiLAgMBAAECggEBALu1Ni/u5Qy++YA8ZcN0s6UXNdhItLmv/q0kZuLQ+9et
+CT8VZfFInLndTdsaXenDKLHdryunviFA8SV+q7P2lMbek+Xs735EiyMnMBFWxLIZ
+FWNGOeQERGL19QCmLEOmEi2b+iWJQHlKaMWpbPXL3w11a+lKjIBNO4ALfoJ5QveZ
+cGMKsJdm/mpqBvLeNeh2eAFk3Gp6sT1g80Ge8NkgyzFBNIqnut0eerM15kPTc6Qz
+12JLaOXUuV3PrcB4PN4nOwrTDg88GDNOQtc1Pc9r4nOHyLfr8X7QEtj1wXSwmOuK
+d6ynMnAmoxVA9wEnupLbil1bzohRzpsTpkmDruYaBEECgYEA/Z09I8D6mt2NVqIE
+KyvLjBK39ijSV9r3/lvB2Ple2OOL5YQEd+yTrIFy+3zdUnDgD1zmNnXjmjvHZ9Lc
+IFf2o06AF84QLNB5gLPdDQkGNFdDqUxljBrfAfE3oANmPS/B0SijMGOOOiDO2FtO
+xl1nfRr78mswuRs9awoUWCdNRKUCgYEA7KaTYKIQW/FEjw9lshp74q5vbn6zoXF5
+7N8VkwI+bBVNvRbM9XZ8qhfgRdu9eXs5oL/N4mSYY54I8fA//pJ0Z2vpmureMm1V
+mL5WBUmSD9DIbAchoK+sRiQhVmNMBQC6cHMABA7RfXvBeGvWrm9pKCS6ZLgLjkjp
+PsmAcaXQcW8CgYEA2inAxljjOwUK6FNGsrxhxIT1qtNC3kCGxE+6WSNq67gSR8Vg
+8qiX//T7LEslOB3RIGYRwxd2St7RkgZZRZllmOWWWuPwFhzf6E7RAL2akLvggGov
+kG4tGEagSw2hjVDfsUT73ExHtMk0Jfmlsg33UC8+PDLpHtLH6qQpDAwC8+ECgYEA
+o+AqOIWhvHmT11l7O915Ip1WzvZwYADbxLsrDnVEUsZh4epTHjvh0kvcY6PqTqCV
+ZIrOANNWb811Nkz/k8NJVoD08PFp0xPBbZeIq/qpachTsfMyRzq/mobUiyUR9Hjv
+ooUQYr78NOApNsG+lWbTNBhS9wI4BlzZIECbcJe5g4MCgYEAndRoy8S+S0Hx/S8a
+O3hzXeDmivmgWqn8NVD4AKOovpkz4PaIVVQbAQkiNfAx8/DavPvjEKAbDezJ4ECV
+j7IsOWtDVI7pd6eF9fTcECwisrda8aUoiOap8AQb48153Vx+g2N4Vy3uH0xJs4cz
+TDALZPOBg8VlV+HEFDP43sp9Bf0=
+-----END PRIVATE KEY-----