diff --git a/cmd/devp2p/dns_route53.go b/cmd/devp2p/dns_route53.go
new file mode 100644
index 0000000000000000000000000000000000000000..1e9b39b0ea95273cd872a3e780e5f993c6c5fa3a
--- /dev/null
+++ b/cmd/devp2p/dns_route53.go
@@ -0,0 +1,260 @@
+// Copyright 2019 The go-ethereum Authors
+// This file is part of go-ethereum.
+//
+// go-ethereum is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// go-ethereum is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
+
+package main
+
+import (
+	"errors"
+	"fmt"
+	"strconv"
+	"strings"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/credentials"
+	"github.com/aws/aws-sdk-go/aws/session"
+	"github.com/aws/aws-sdk-go/service/route53"
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/p2p/dnsdisc"
+	"gopkg.in/urfave/cli.v1"
+)
+
+var (
+	route53AccessKeyFlag = cli.StringFlag{
+		Name:   "access-key-id",
+		Usage:  "AWS Access Key ID",
+		EnvVar: "AWS_ACCESS_KEY_ID",
+	}
+	route53AccessSecretFlag = cli.StringFlag{
+		Name:   "access-key-secret",
+		Usage:  "AWS Access Key Secret",
+		EnvVar: "AWS_SECRET_ACCESS_KEY",
+	}
+	route53ZoneIDFlag = cli.StringFlag{
+		Name:  "zone-id",
+		Usage: "Route53 Zone ID",
+	}
+)
+
+type route53Client struct {
+	api    *route53.Route53
+	zoneID string
+}
+
+// newRoute53Client sets up a Route53 API client from command line flags.
+func newRoute53Client(ctx *cli.Context) *route53Client {
+	akey := ctx.String(route53AccessKeyFlag.Name)
+	asec := ctx.String(route53AccessSecretFlag.Name)
+	if akey == "" || asec == "" {
+		exit(fmt.Errorf("need Route53 Access Key ID and secret proceed"))
+	}
+	config := &aws.Config{Credentials: credentials.NewStaticCredentials(akey, asec, "")}
+	session, err := session.NewSession(config)
+	if err != nil {
+		exit(fmt.Errorf("can't create AWS session: %v", err))
+	}
+	return &route53Client{
+		api:    route53.New(session),
+		zoneID: ctx.String(route53ZoneIDFlag.Name),
+	}
+}
+
+// deploy uploads the given tree to Route53.
+func (c *route53Client) deploy(name string, t *dnsdisc.Tree) error {
+	if err := c.checkZone(name); err != nil {
+		return err
+	}
+
+	// Compute DNS changes.
+	records := t.ToTXT(name)
+	changes, err := c.computeChanges(name, records)
+	if err != nil {
+		return err
+	}
+	if len(changes) == 0 {
+		log.Info("No DNS changes needed")
+		return nil
+	}
+
+	// Submit change request.
+	log.Info(fmt.Sprintf("Submitting %d changes to Route53", len(changes)))
+	batch := new(route53.ChangeBatch)
+	batch.SetChanges(changes)
+	batch.SetComment(fmt.Sprintf("enrtree update of %s at seq %d", name, t.Seq()))
+	req := &route53.ChangeResourceRecordSetsInput{HostedZoneId: &c.zoneID, ChangeBatch: batch}
+	resp, err := c.api.ChangeResourceRecordSets(req)
+	if err != nil {
+		return err
+	}
+
+	// Wait for the change to be applied.
+	log.Info(fmt.Sprintf("Waiting for change request %s", *resp.ChangeInfo.Id))
+	wreq := &route53.GetChangeInput{Id: resp.ChangeInfo.Id}
+	return c.api.WaitUntilResourceRecordSetsChanged(wreq)
+}
+
+// checkZone verifies zone information for the given domain.
+func (c *route53Client) checkZone(name string) (err error) {
+	if c.zoneID == "" {
+		c.zoneID, err = c.findZoneID(name)
+	}
+	return err
+}
+
+// findZoneID searches for the Zone ID containing the given domain.
+func (c *route53Client) findZoneID(name string) (string, error) {
+	log.Info(fmt.Sprintf("Finding Route53 Zone ID for %s", name))
+	var req route53.ListHostedZonesByNameInput
+	for {
+		resp, err := c.api.ListHostedZonesByName(&req)
+		if err != nil {
+			return "", err
+		}
+		for _, zone := range resp.HostedZones {
+			if isSubdomain(name, *zone.Name) {
+				return *zone.Id, nil
+			}
+		}
+		if !*resp.IsTruncated {
+			break
+		}
+		req.DNSName = resp.NextDNSName
+		req.HostedZoneId = resp.NextHostedZoneId
+	}
+	return "", errors.New("can't find zone ID for " + name)
+}
+
+// computeChanges creates DNS changes for the given record.
+func (c *route53Client) computeChanges(name string, records map[string]string) ([]*route53.Change, error) {
+	// Convert all names to lowercase.
+	lrecords := make(map[string]string, len(records))
+	for name, r := range records {
+		lrecords[strings.ToLower(name)] = r
+	}
+	records = lrecords
+
+	// Get existing records.
+	existing, err := c.collectRecords(name)
+	if err != nil {
+		return nil, err
+	}
+	log.Info(fmt.Sprintf("Found %d TXT records", len(existing)))
+
+	var changes []*route53.Change
+	for path, val := range records {
+		ttl := 1
+		if path != name {
+			ttl = 2147483647
+		}
+
+		prevRecords, exists := existing[path]
+		prevValue := combineTXT(prevRecords)
+		if !exists {
+			// Entry is unknown, push a new one
+			log.Info(fmt.Sprintf("Creating %s = %q", path, val))
+			changes = append(changes, newTXTChange("CREATE", path, ttl, splitTXT(val)))
+		} else if prevValue != val {
+			// Entry already exists, only change its content.
+			log.Info(fmt.Sprintf("Updating %s from %q to %q", path, prevValue, val))
+			changes = append(changes, newTXTChange("UPSERT", path, ttl, splitTXT(val)))
+		} else {
+			log.Info(fmt.Sprintf("Skipping %s = %q", path, val))
+		}
+	}
+
+	// Iterate over the old records and delete anything stale.
+	for path, values := range existing {
+		if _, ok := records[path]; ok {
+			continue
+		}
+		// Stale entry, nuke it.
+		log.Info(fmt.Sprintf("Deleting %s = %q", path, combineTXT(values)))
+		changes = append(changes, newTXTChange("DELETE", path, 1, values))
+	}
+	return changes, nil
+}
+
+// collectRecords collects all TXT records below the given name.
+func (c *route53Client) collectRecords(name string) (map[string][]string, error) {
+	log.Info(fmt.Sprintf("Retrieving existing TXT records on %s (%s)", name, c.zoneID))
+	var req route53.ListResourceRecordSetsInput
+	req.SetHostedZoneId(c.zoneID)
+	existing := make(map[string][]string)
+	err := c.api.ListResourceRecordSetsPages(&req, func(resp *route53.ListResourceRecordSetsOutput, last bool) bool {
+		for _, set := range resp.ResourceRecordSets {
+			if !isSubdomain(*set.Name, name) || *set.Type != "TXT" {
+				continue
+			}
+			name := strings.TrimSuffix(*set.Name, ".")
+			for _, rec := range set.ResourceRecords {
+				existing[name] = append(existing[name], *rec.Value)
+			}
+		}
+		return true
+	})
+	return existing, err
+}
+
+// newTXTChange creates a change to a TXT record.
+func newTXTChange(action, name string, ttl int, values []string) *route53.Change {
+	var c route53.Change
+	var r route53.ResourceRecordSet
+	var rrs []*route53.ResourceRecord
+	for _, val := range values {
+		rr := new(route53.ResourceRecord)
+		rr.SetValue(val)
+		rrs = append(rrs, rr)
+	}
+	r.SetType("TXT")
+	r.SetName(name)
+	r.SetTTL(int64(ttl))
+	r.SetResourceRecords(rrs)
+	c.SetAction(action)
+	c.SetResourceRecordSet(&r)
+	return &c
+}
+
+// isSubdomain returns true if name is a subdomain of domain.
+func isSubdomain(name, domain string) bool {
+	domain = strings.TrimSuffix(domain, ".")
+	name = strings.TrimSuffix(name, ".")
+	return strings.HasSuffix("."+name, "."+domain)
+}
+
+// combineTXT concatenates the given quoted strings into a single unquoted string.
+func combineTXT(values []string) string {
+	result := ""
+	for _, v := range values {
+		if v[0] == '"' {
+			v = v[1 : len(v)-1]
+		}
+		result += v
+	}
+	return result
+}
+
+// splitTXT splits value into a list of quoted 255-character strings.
+func splitTXT(value string) []string {
+	var result []string
+	for len(value) > 0 {
+		rlen := len(value)
+		if rlen > 253 {
+			rlen = 253
+		}
+		result = append(result, strconv.Quote(value[:rlen]))
+		value = value[rlen:]
+	}
+	return result
+}
diff --git a/cmd/devp2p/dnscmd.go b/cmd/devp2p/dnscmd.go
index f245104053c393ffc2b8a39664e77064f2f3acd8..287d6e6c76a16d70bc446bbddc3fa1dc3e696133 100644
--- a/cmd/devp2p/dnscmd.go
+++ b/cmd/devp2p/dnscmd.go
@@ -42,6 +42,7 @@ var (
 			dnsSignCommand,
 			dnsTXTCommand,
 			dnsCloudflareCommand,
+			dnsRoute53Command,
 		},
 	}
 	dnsSyncCommand = cli.Command{
@@ -66,11 +67,18 @@ var (
 	}
 	dnsCloudflareCommand = cli.Command{
 		Name:      "to-cloudflare",
-		Usage:     "Deploy DNS TXT records to cloudflare",
+		Usage:     "Deploy DNS TXT records to CloudFlare",
 		ArgsUsage: "<tree-directory>",
 		Action:    dnsToCloudflare,
 		Flags:     []cli.Flag{cloudflareTokenFlag, cloudflareZoneIDFlag},
 	}
+	dnsRoute53Command = cli.Command{
+		Name:      "to-route53",
+		Usage:     "Deploy DNS TXT records to Amazon Route53",
+		ArgsUsage: "<tree-directory>",
+		Action:    dnsToRoute53,
+		Flags:     []cli.Flag{route53AccessKeyFlag, route53AccessSecretFlag, route53ZoneIDFlag},
+	}
 )
 
 var (
@@ -194,6 +202,19 @@ func dnsToCloudflare(ctx *cli.Context) error {
 	return client.deploy(domain, t)
 }
 
+// dnsToRoute53 peforms dnsRoute53Command.
+func dnsToRoute53(ctx *cli.Context) error {
+	if ctx.NArg() < 1 {
+		return fmt.Errorf("need tree definition directory as argument")
+	}
+	domain, t, err := loadTreeDefinitionForExport(ctx.Args().Get(0))
+	if err != nil {
+		return err
+	}
+	client := newRoute53Client(ctx)
+	return client.deploy(domain, t)
+}
+
 // loadSigningKey loads a private key in Ethereum keystore format.
 func loadSigningKey(keyfile string) *ecdsa.PrivateKey {
 	keyjson, err := ioutil.ReadFile(keyfile)
diff --git a/go.mod b/go.mod
index a280949e947698e6f594ddb185de213fb6c25a1c..223086f8c5f97efd94c64bd79d91eb16c641aa54 100644
--- a/go.mod
+++ b/go.mod
@@ -9,6 +9,7 @@ require (
 	github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect
 	github.com/VictoriaMetrics/fastcache v1.5.3
 	github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847
+	github.com/aws/aws-sdk-go v1.25.48
 	github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6
 	github.com/cespare/cp v0.1.0
 	github.com/cespare/xxhash/v2 v2.1.1 // indirect
diff --git a/go.sum b/go.sum
index 515207bca3874a936e620bebc073e484ce7559ed..edbb5ea2e0908c7ca37dd25af738952ac25672d2 100644
--- a/go.sum
+++ b/go.sum
@@ -33,6 +33,8 @@ github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah
 github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
 github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847 h1:rtI0fD4oG/8eVokGVPYJEW1F88p1ZNgXiEIs9thEE4A=
 github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ=
+github.com/aws/aws-sdk-go v1.25.48 h1:J82DYDGZHOKHdhx6hD24Tm30c2C3GchYGfN0mf9iKUk=
+github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6 h1:Eey/GGQ/E5Xp1P2Lyx1qj007hLZfbi0+CoVeJruGCtI=
 github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ=
@@ -99,6 +101,7 @@ github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883 h1:FSeK4fZCo
 github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY=
 github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 h1:6OvNmYgJyexcZ3pYbTI9jWx5tHo1Dee/tWbLMfPe2TA=
 github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
+github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21 h1:F/iKcka0K2LgnKy/fgSBf235AETtm1n1TvBzqu40LE0=
 github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 h1:I/yrLt2WilKxlQKCM52clh5rGzTKpVctGT1lH4Dc8Jw=