diff --git a/README.md b/README.md
index 52538073257d9848e3b1c3b91f95ef039a39af28..6594fbe95a714d477c0cfbfd94a33a3d195731b6 100644
--- a/README.md
+++ b/README.md
@@ -251,6 +251,11 @@ log configuration
     "enable": false,
     "protocol": "udp",
     "address": "localhost:514"
+  },
+  "kafka": {
+    "enable": false,
+    "brokers": ["127.0.0.1:9092"],
+    "topic": "redins"
   }
 }
 ~~~
@@ -263,6 +268,7 @@ log configuration
 * path : log output file path
 * sentry : sentry hook configurations
 * syslog : syslog hook configurations
+* kafka : kafka hook configurations
 
 ### rate limit 
 rate limit connfiguration
diff --git a/VERSION b/VERSION
index 3eefcb9dd5b38e2c1dc061052455dd97bcd51e6c..ee90284c27f187a315f1267b063fa81b5b84f613 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.0.0
+1.0.4
diff --git a/config.json b/config.json
index 7cdfabef499d01e5dbe9629e536f96d1e174b735..76796fb9eff23638e48103e8e6e5b8b866ba9404 100644
--- a/config.json
+++ b/config.json
@@ -7,8 +7,14 @@
     },
     {
       "ip": "127.0.0.1",
-      "port": 2053,
-      "protocol": "udp"
+      "port": 10853,
+      "protocol": "tcp",
+      "tls": {
+          "enable": true,
+          "cert_path": "",
+          "key_path": "",
+          "ca_path": ""
+      }
     }
   ],
   "handler": {
@@ -32,7 +38,12 @@
       "target": "file",
       "level": "info",
       "path": "/tmp/redins.log",
-      "time_format": "2006-01-02 15:04:05"
+      "time_format": "2006-01-02 15:04:05",
+      "kafka": {
+          "enable": true,
+          "topic": "redins",
+          "brokers": ["127.0.0.1:9092"]
+      }
     },
     "upstream": [{
       "ip": "1.1.1.1",
@@ -51,7 +62,7 @@
       "asn_db": "geoIsp.mmdb"
     },
     "healthcheck": {
-      "enable": true,
+      "enable": false,
       "max_requests": 5,
       "max_pending_requests": 100,
       "update_interval": 600,
diff --git a/handler/dns_types.go b/handler/dns_types.go
index 3d25bf52bb54c65ace2a556c6ff113c2b3d41b35..2326cee424ecd6de606142dc161a97b1dad8bea5 100644
--- a/handler/dns_types.go
+++ b/handler/dns_types.go
@@ -29,9 +29,10 @@ type Record struct {
 }
 
 type ZoneKey struct {
-	PublicKey  string `json:"public_key,omitmpty"`
-	PrivateKey string `json:"private_key,omitempty"`
-	Algorithm  uint8  `json:"algorithm,omitempty"`
+	DnsKey        *dns.DNSKEY
+	PrivateKey    crypto.PrivateKey
+	KeyInception  uint32
+	KeyExpiration uint32
 }
 
 type ZoneConfig struct {
@@ -45,11 +46,9 @@ type Zone struct {
 	Name          string
 	Config        ZoneConfig
 	Locations     map[string]struct{}
-	DnsKey        *dns.DNSKEY
+	ZSK           *ZoneKey
+	KSK           *ZoneKey
 	DnsKeySig     dns.RR
-	PrivateKey    crypto.PrivateKey
-	KeyInception  uint32
-	KeyExpiration uint32
 }
 
 type IP_RRSet struct {
diff --git a/handler/dnssec.go b/handler/dnssec.go
index 0a88c6ba0faf69fde318e54d43e769bdcf76e47e..275d5886e82d220969a1cc90b9127414521453b4 100644
--- a/handler/dnssec.go
+++ b/handler/dnssec.go
@@ -13,23 +13,72 @@ var (
 	NSecTypes = []uint16{dns.TypeRRSIG, dns.TypeNSEC}
 )
 
-func Sign(rrs []dns.RR, name string, zone *Zone, ttl uint32) (*dns.RRSIG, error) {
+type rrset struct {
+	qname string
+	qtype uint16
+}
+
+func splitSets(rrs []dns.RR) map[rrset][]dns.RR {
+	m := make(map[rrset][]dns.RR)
+
+	for _, r := range rrs {
+		if r.Header().Rrtype == dns.TypeRRSIG || r.Header().Rrtype == dns.TypeOPT {
+			continue
+		}
+
+		if s, ok := m[rrset{r.Header().Name, r.Header().Rrtype}]; ok {
+			s = append(s, r)
+			m[rrset{r.Header().Name, r.Header().Rrtype}] = s
+			continue
+		}
+
+		s := make([]dns.RR, 1, 3)
+		s[0] = r
+		m[rrset{r.Header().Name, r.Header().Rrtype}] = s
+	}
+
+	if len(m) > 0 {
+		return m
+	}
+	return nil
+}
+
+func Sign(rrs []dns.RR, qname string, record *Record) []dns.RR {
+	var res []dns.RR
+	sets := splitSets(rrs)
+	for _, set := range sets {
+		res = append(res, set...)
+		switch set[0].Header().Rrtype {
+		case dns.TypeRRSIG, dns.TypeOPT:
+			continue
+		case dns.TypeDNSKEY:
+			res = append(res, record.Zone.DnsKeySig)
+		default:
+			if rrsig, err := sign(set, qname, record.Zone.ZSK, set[0].Header().Ttl); err == nil {
+				res = append(res, rrsig)
+			}
+		}
+	}
+	return res
+}
+
+func sign(rrs []dns.RR, name string, key *ZoneKey, ttl uint32) (*dns.RRSIG, error) {
 	rrsig := &dns.RRSIG{
 		Hdr:        dns.RR_Header{name, dns.TypeRRSIG, dns.ClassINET, ttl, 0},
-		Inception:  zone.KeyInception,
-		Expiration: zone.KeyExpiration,
-		KeyTag:     zone.DnsKey.KeyTag(),
-		SignerName: zone.DnsKey.Hdr.Name,
-		Algorithm:  zone.DnsKey.Algorithm,
+		Inception:  key.KeyInception,
+		Expiration: key.KeyExpiration,
+		KeyTag:     key.DnsKey.KeyTag(),
+		SignerName: key.DnsKey.Hdr.Name,
+		Algorithm:  key.DnsKey.Algorithm,
 	}
 	switch rrsig.Algorithm {
 	case dns.RSAMD5, dns.RSASHA1, dns.RSASHA1NSEC3SHA1, dns.RSASHA256, dns.RSASHA512:
-		if err := rrsig.Sign(zone.PrivateKey.(*rsa.PrivateKey), rrs); err != nil {
+		if err := rrsig.Sign(key.PrivateKey.(*rsa.PrivateKey), rrs); err != nil {
 			logger.Default.Errorf("sign failed : %s", err)
 			return nil, err
 		}
 	case dns.ECDSAP256SHA256, dns.ECDSAP384SHA384:
-		if err := rrsig.Sign(zone.PrivateKey.(*ecdsa.PrivateKey), rrs); err != nil {
+		if err := rrsig.Sign(key.PrivateKey.(*ecdsa.PrivateKey), rrs); err != nil {
 			logger.Default.Errorf("sign failed : %s", err)
 			return nil, err
 		}
@@ -42,16 +91,12 @@ func Sign(rrs []dns.RR, name string, zone *Zone, ttl uint32) (*dns.RRSIG, error)
 	return rrsig, nil
 }
 
-func NSec(name string, zone *Zone) ([]dns.RR, error) {
+func NSec(name string, zone *Zone) dns.RR {
 	nsec := &dns.NSEC{
 		Hdr:        dns.RR_Header{name, dns.TypeNSEC, dns.ClassINET, zone.Config.SOA.MinTtl, 0},
 		NextDomain: "\\000." + name,
 		TypeBitMap: NSecTypes,
 	}
-	sigs, err := Sign([]dns.RR{nsec}, name, zone, zone.Config.SOA.MinTtl)
-	if err != nil {
-		return nil, err
-	}
 
-	return []dns.RR{nsec, sigs}, nil
+	return nsec
 }
diff --git a/handler/dnssec_test.go b/handler/dnssec_test.go
index 07b4782279a88d81820b944b363d2ff6193d6146..f879af0f7ed796cef35d727cbe87ee0ea3bf8a7d 100644
--- a/handler/dnssec_test.go
+++ b/handler/dnssec_test.go
@@ -61,7 +61,7 @@ var dnssecEntries = [][]string{
 	},
 }
 
-var dnssecKeyPriv = string(
+var zskPriv = string(
 	`Private-key-format: v1.3
 Algorithm: 5 (RSASHA1)
 Modulus: oqwXm/EF8q6p5Rrj66Bbft+0Vk7Kj6TuvZp4nNl0htiT/8/92kIcri5gbxnV2v+p6jXYQI1Vx/vqP5cB0kPzjUQuJFVpm14fxOp89D6N0fPXR7xJ+SHs5nigHBIJdaP5
@@ -77,14 +77,40 @@ Publish: 20180717134704
 Activate: 20180717134704
 `)
 
-var dnssecKeyPub = string("dnssec_test.com. IN DNSKEY 256 3 5 AwEAAaKsF5vxBfKuqeUa4+ugW37ftFZOyo+k7r2aeJzZdIbYk//P/dpC HK4uYG8Z1dr/qeo12ECNVcf76j+XAdJD841ELiRVaZteH8TqfPQ+jdHz 10e8Sfkh7OZ4oBwSCXWj+Q==")
+var zskPub = string("dnssec_test.com. IN DNSKEY 256 3 5 AwEAAaKsF5vxBfKuqeUa4+ugW37ftFZOyo+k7r2aeJzZdIbYk//P/dpC HK4uYG8Z1dr/qeo12ECNVcf76j+XAdJD841ELiRVaZteH8TqfPQ+jdHz 10e8Sfkh7OZ4oBwSCXWj+Q==")
 
 var dnskeyQuery = test.Case{
 	Do:    true,
 	Qname: "dnssec_test.com", Qtype: dns.TypeDNSKEY,
 }
 
+var kskPriv = string(
+	`Private-key-format: v1.3
+Algorithm: 5 (RSASHA1)
+Modulus: 5WuOIP3GHID5Qmed6L+2ehBCkusTAXNv9uUfpzzTJHsA+bBesZSFsRNzMAV2drM7fApcL5IgNqrhb5twxu1/+cZj2Ld3PALbkENzn/erTl4A4uQdSWdkj8KnaLiJQPaT
+PublicExponent: AQAB
+PrivateExponent: BxiDhduzg/AtRXOE+8zqLO5R0M96gAH9BYripr6H3Un8prxgwWdRlz99wY95sYQrlNWr+4hhvikuOc9FjpXGg8E63iCNaZsVd/l8RvLGCtRPMtOEWhOecKe3kktHMUxp
+Prime1: 9EWCZ3wwK2q7nsts12QuFGBTH/SOgHiaw9ieAn+mOA679BlIWXjeUoA5Hlj+ob31
+Prime2: 8G9/lMOO+xgwjU7lQ5teFGmmNb2JXB/nP3pWQURdy+Chnb8wrcHALJGW1G7DAMVn
+Exponent1: jroSoQ7iQmwh5n3sQcpqVkOWLmTB4vUVUPvAD6uwXq7VSaKAMK88EC6VsVLErZMF
+Exponent2: qIlPwgTOzf3n0rXSCXD4IpDoHFWO2o/Wdm2X1spIgWglgcEKK1JcFiG7u48ki/7T
+Coefficient: QCGY0yr+kkmOZfUoL9YCCgau/xjyEPRZgiGTfIy0PtGGMDKfUswJ+1KWI9Jue3E5
+Created: 20190518113600
+Publish: 20190518113600
+Activate: 20190518113600
+`)
+
+var kskPub = string("dnssec_test.com. IN DNSKEY 257 3 5 AwEAAeVrjiD9xhyA+UJnnei/tnoQQpLrEwFzb/blH6c80yR7APmwXrGU hbETczAFdnazO3wKXC+SIDaq4W+bcMbtf/nGY9i3dzwC25BDc5/3q05e AOLkHUlnZI/Cp2i4iUD2kw==")
+
 var dnssecTestCases = []test.Case{
+	{
+		Qname: "dnssec_test.com.", Qtype: dns.TypeDNSKEY,
+		Answer: []dns.RR{
+			test.DNSKEY("dnssec_test.com.	3600	IN	DNSKEY	256 3 5 AwEAAaKsF5vxBfKuqeUa4+ugW37ftFZOyo+k7r2aeJzZdIbYk//P/dpCHK4uYG8Z1dr/qeo12ECNVcf76j+XAdJD841ELiRVaZteH8TqfPQ+jdHz10e8Sfkh7OZ4oBwSCXWj+Q=="),
+			test.DNSKEY("dnssec_test.com.	3600	IN	DNSKEY	257 3 5 AwEAAeVrjiD9xhyA+UJnnei/tnoQQpLrEwFzb/blH6c80yR7APmwXrGUhbETczAFdnazO3wKXC+SIDaq4W+bcMbtf/nGY9i3dzwC25BDc5/3q05eAOLkHUlnZI/Cp2i4iUD2kw=="),
+			test.RRSIG("dnssec_test.com.	3600	IN	RRSIG	DNSKEY 5 2 3600 20190527081109 20190519051109 37456 dnssec_test.com. oVwtVEf9eOkcuSJlsH0OSBUvLOxgKM1pIAe7v717oRyCoyC+FIG5uGsdrZWhgklh/fpEmRdJQ+nHXKWT/son8zvxAoskuIIp49wwgvcS400IoHiyjIY0BHNTFPvsPdy0"),
+		},
+	},
 	{
 		Qname: "x.dnssec_test.com.", Qtype: dns.TypeA,
 		Answer: []dns.RR{
@@ -280,12 +306,15 @@ func TestDNSSEC(t *testing.T) {
 		}
 	}
 	h.Redis.Set("redins:zones:"+dnssecZone+":config", dnssecConfig)
-	h.Redis.Set("redins:zones:"+dnssecZone+":pub", dnssecKeyPub)
-	h.Redis.Set("redins:zones:"+dnssecZone+":priv", dnssecKeyPriv)
+	h.Redis.Set("redins:zones:"+dnssecZone+":zsk:pub", zskPub)
+	h.Redis.Set("redins:zones:"+dnssecZone+":zsk:priv", zskPriv)
+	h.Redis.Set("redins:zones:"+dnssecZone+":ksk:pub", kskPub)
+	h.Redis.Set("redins:zones:"+dnssecZone+":ksk:priv", kskPriv)
 	h.Redis.SAdd("redins:zones", dnssecZone)
 	h.LoadZones()
 
-	var dnskey dns.RR
+	var zsk dns.RR
+	var ksk dns.RR
 	{
 		r := dnskeyQuery.Msg()
 		w := test.NewRecorder(&test.ResponseWriter{})
@@ -293,8 +322,18 @@ func TestDNSSEC(t *testing.T) {
 		h.HandleRequest(&state)
 		resp := w.Msg
 		fmt.Println(resp.Answer)
-		dnskey = resp.Answer[0]
+		for _, answer := range resp.Answer {
+			if key, ok := answer.(*dns.DNSKEY); ok {
+				if key.Flags == 256 {
+					zsk = answer
+				} else if key.Flags == 257 {
+					ksk = answer
+				}
+			}
+		}
 	}
+	// fmt.Println("zsk is ", zsk.String())
+	// fmt.Println("ksk is ", ksk.String())
 
 	for i, tc0 := range dnssecTestCases {
 		tc := test.Case{
@@ -316,11 +355,7 @@ func TestDNSSEC(t *testing.T) {
 		state := request.Request{W: w, Req: r}
 		h.HandleRequest(&state)
 		resp := w.Msg
-		if i == 7 {
-			fmt.Println("here")
-		}
-		for zz, rrs := range [][]dns.RR{tc0.Answer, tc0.Ns, resp.Answer, resp.Ns} {
-			fmt.Println(zz)
+		for _, rrs := range [][]dns.RR{tc0.Answer, tc0.Ns, resp.Answer, resp.Ns} {
 			s := 0
 			e := 1
 			for {
@@ -328,10 +363,17 @@ func TestDNSSEC(t *testing.T) {
 					break
 				}
 				if rrsig, ok := rrs[e].(*dns.RRSIG); ok {
-					fmt.Printf("s = %d, e = %d\n", s, e)
-					if rrsig.Verify(dnskey.(*dns.DNSKEY), rrs[s:e]) != nil {
-						fmt.Println("fail")
-						t.Fail()
+					//fmt.Printf("s = %d, e = %d\n", s, e)
+					if tc.Qtype == dns.TypeDNSKEY {
+						if rrsig.Verify(ksk.(*dns.DNSKEY), rrs[s:e]) != nil {
+							fmt.Println("fail")
+							t.Fail()
+						}
+					} else {
+						if rrsig.Verify(zsk.(*dns.DNSKEY), rrs[s:e]) != nil {
+							fmt.Println("fail")
+							t.Fail()
+						}
 					}
 					s = e + 1
 					e = s + 1
@@ -340,10 +382,11 @@ func TestDNSSEC(t *testing.T) {
 				}
 			}
 		}
-		fmt.Println("dddd")
-		if test.SortAndCheck(resp, tc) != nil {
+		//fmt.Println("dddd")
+		if err := test.SortAndCheck(resp, tc); err != nil {
+			fmt.Println(err, resp.Answer, tc.Answer)
 			t.Fail()
 		}
-		fmt.Println("xxxx")
+		//fmt.Println("xxxx")
 	}
 }
diff --git a/handler/geoip.go b/handler/geoip.go
index ded9041fc19b984c5754e41f8139bc0e69fb26ac..78ed37a3ce7ac6975a976ce3a69fd0f4afdf9a7f 100644
--- a/handler/geoip.go
+++ b/handler/geoip.go
@@ -48,7 +48,7 @@ func (g *GeoIp) GetSameCountry(sourceIp net.IP, ips []IP_RR, logData map[string]
 		logger.Default.Error("getSameCountry failed")
 		return ips
 	}
-	logData["SourceCountry"] = sourceCountry
+	logData["source_country"] = sourceCountry
 
 	var result []IP_RR
 	if sourceCountry != "" {
@@ -93,7 +93,7 @@ func (g *GeoIp) GetSameASN(sourceIp net.IP, ips []IP_RR, logData map[string]inte
 		logger.Default.Error("getSameASN failed")
 		return ips
 	}
-	logData["SourceASN"] = sourceASN
+	logData["source_asn"] = sourceASN
 
 	var result []IP_RR
 	if sourceASN != 0 {
diff --git a/handler/handler.go b/handler/handler.go
index 48b6bad1803fd0b415fff1490b57aaf161c5ec7a..c6371303f5af580a18042a5af51b082f8cb15f0e 100644
--- a/handler/handler.go
+++ b/handler/handler.go
@@ -109,18 +109,18 @@ func (h *DnsRequestHandler) HandleRequest(state *request.Request) {
 	requestStartTime := time.Now()
 
 	logData := map[string]interface{}{
-		"SourceIP": state.IP(),
-		"Record":   state.Name(),
-		"Type":     state.Type(),
+		"source_ip": state.IP(),
+		"record":    state.Name(),
+		"type":      state.Type(),
 	}
-	logData["ClientSubnet"] = GetSourceSubnet(state)
+	logData["client_subnet"] = GetSourceSubnet(state)
 
 	if h.Config.LogSourceLocation {
 		sourceIP := GetSourceIp(state)
 		_, _, sourceCountry, _ := h.geoip.GetGeoLocation(sourceIP)
-		logData["SourceCountry"] = sourceCountry
+		logData["source_country"] = sourceCountry
 		sourceASN, _ := h.geoip.GetASN(sourceIP)
-		logData["SourceASN"] = sourceASN
+		logData["source_asn"] = sourceASN
 	}
 
 	auth := true
@@ -132,12 +132,16 @@ func (h *DnsRequestHandler) HandleRequest(state *request.Request) {
 	var authority []dns.RR
 	record, localRes = h.FetchRecord(qname, logData)
 	originalRecord := record
-	secured := state.Do() && record != nil && record.Zone.Config.DnsSec
 	if record != nil {
-		logData["DomainId"] = record.Zone.Config.DomainId
+		logData["domain_uuid"] = record.Zone.Config.DomainId
 		if qtype != dns.TypeCNAME {
-			// TODO: check for cname loop
+			count := 0
 			for {
+				if count >= 10 {
+					answers = []dns.RR{}
+					localRes = dns.RcodeServerFailure
+					break
+				}
 				if localRes != dns.RcodeSuccess {
 					break
 				}
@@ -145,10 +149,14 @@ func (h *DnsRequestHandler) HandleRequest(state *request.Request) {
 					break
 				}
 				if !record.Zone.Config.CnameFlattening {
-					answers = AppendRR(answers, h.CNAME(qname, record), qname, record, secured)
+					answers = append(answers, h.CNAME(qname, record)...)
+					if h.Matches(record.CNAME.Host) != originalRecord.Zone.Name {
+						break
+					}
 					qname = record.CNAME.Host
 				}
 				record, localRes = h.FetchRecord(record.CNAME.Host, logData)
+				count++
 			}
 		}
 	}
@@ -162,7 +170,7 @@ func (h *DnsRequestHandler) HandleRequest(state *request.Request) {
 					anameAnswer, anameRes := h.FetchRecord(record.ANAME.Location, logData)
 					if anameRes == dns.RcodeSuccess {
 						ips := h.Filter(state, &anameAnswer.A, logData)
-						answers = AppendRR(answers, h.A(qname, anameAnswer, ips), qname, record, secured)
+						answers = append(answers, h.A(qname, anameAnswer, ips)...)
 					} else {
 						upstreamAnswers, upstreamRes := h.upstream.Query(record.ANAME.Location, dns.TypeA)
 						if upstreamRes == dns.RcodeSuccess {
@@ -173,14 +181,14 @@ func (h *DnsRequestHandler) HandleRequest(state *request.Request) {
 									anameRecord = append(anameRecord, &dns.A{A: a.A, Hdr: dns.RR_Header{Rrtype: dns.TypeA, Name: qname, Ttl: a.Hdr.Ttl, Class: dns.ClassINET, Rdlength: 0}})
 								}
 							}
-							answers = AppendRR(answers, anameRecord, qname, record, secured)
+							answers = append(answers, anameRecord...)
 						}
 						res = upstreamRes
 					}
 				}
 			} else {
 				ips := h.Filter(state, &record.A, logData)
-				answers = AppendRR(answers, h.A(qname, record, ips), qname, record, secured)
+				answers = append(answers, h.A(qname, record, ips)...)
 			}
 		case dns.TypeAAAA:
 			if len(record.AAAA.Data) == 0 {
@@ -188,7 +196,7 @@ func (h *DnsRequestHandler) HandleRequest(state *request.Request) {
 					anameAnswer, anameRes := h.FetchRecord(record.ANAME.Location, logData)
 					if anameRes == dns.RcodeSuccess {
 						ips := h.Filter(state, &anameAnswer.AAAA, logData)
-						answers = AppendRR(answers, h.AAAA(qname, anameAnswer, ips), qname, record, secured)
+						answers = append(answers, h.AAAA(qname, anameAnswer, ips)...)
 					} else {
 						upstreamAnswers, upstreamRes := h.upstream.Query(record.ANAME.Location, dns.TypeAAAA)
 						if upstreamRes == dns.RcodeSuccess {
@@ -199,39 +207,39 @@ func (h *DnsRequestHandler) HandleRequest(state *request.Request) {
 									anameRecord = append(anameRecord, &dns.AAAA{AAAA: a.AAAA, Hdr: dns.RR_Header{Rrtype: dns.TypeAAAA, Name: qname, Ttl: a.Hdr.Ttl, Class: dns.ClassINET, Rdlength: 0}})
 								}
 							}
-							answers = AppendRR(answers, anameRecord, qname, record, secured)
+							answers = append(answers, anameRecord...)
 						}
 						res = upstreamRes
 					}
 				}
 			} else {
 				ips := h.Filter(state, &record.AAAA, logData)
-				answers = AppendRR(answers, h.AAAA(qname, record, ips), qname, record, secured)
+				answers = append(answers, h.AAAA(qname, record, ips)...)
 			}
 		case dns.TypeCNAME:
-			answers = AppendRR(answers, h.CNAME(qname, record), qname, record, secured)
+			answers = append(answers, h.CNAME(qname, record)...)
 		case dns.TypeTXT:
-			answers = AppendRR(answers, h.TXT(qname, record), qname, record, secured)
+			answers = append(answers, h.TXT(qname, record)...)
 		case dns.TypeNS:
-			answers = AppendRR(answers, h.NS(qname, record), qname, record, secured)
+			answers = append(answers, h.NS(qname, record)...)
 		case dns.TypeMX:
-			answers = AppendRR(answers, h.MX(qname, record), qname, record, secured)
+			answers = append(answers, h.MX(qname, record)...)
 		case dns.TypeSRV:
-			answers = AppendRR(answers, h.SRV(qname, record), qname, record, secured)
+			answers = append(answers, h.SRV(qname, record)...)
 		case dns.TypeCAA:
 			caaRecord := h.FindCAA(record)
 			if caaRecord != nil {
-				answers = AppendRR(answers, h.CAA(qname, caaRecord), qname, caaRecord, secured)
+				answers = append(answers, h.CAA(qname, caaRecord)...)
 			}
 		case dns.TypePTR:
-			answers = AppendRR(answers, h.PTR(qname, record), qname, record, secured)
+			answers = append(answers, h.PTR(qname, record)...)
 		case dns.TypeTLSA:
-			answers = AppendRR(answers, h.TLSA(qname, record), qname, record, secured)
+			answers = append(answers, h.TLSA(qname, record)...)
 		case dns.TypeSOA:
-			answers = AppendSOA(answers, record.Zone, secured)
+			answers = append(answers, record.Zone.Config.SOA.Data)
 		case dns.TypeDNSKEY:
-			if secured {
-				answers = []dns.RR{record.Zone.DnsKey, record.Zone.DnsKeySig}
+			if record.Zone.Config.DnsSec {
+				answers = []dns.RR{record.Zone.ZSK.DnsKey, record.Zone.KSK.DnsKey}
 			}
 		default:
 			answers = []dns.RR{}
@@ -240,19 +248,14 @@ func (h *DnsRequestHandler) HandleRequest(state *request.Request) {
 		}
 		if len(answers) == 0 {
 			if originalRecord.CNAME != nil {
-				answers = AppendRR(answers, h.CNAME(qname, record), qname, record, secured)
+				answers = append(answers, h.CNAME(qname, record)...)
 			} else {
-				authority = AppendSOA(authority, originalRecord.Zone, secured)
-				authority = AppendNSEC(authority, originalRecord.Zone, qname, secured)
+				authority = append(authority, originalRecord.Zone.Config.SOA.Data)
 			}
 		}
 	} else if localRes == dns.RcodeNameError {
 		answers = []dns.RR{}
-		authority = AppendSOA(authority, originalRecord.Zone, secured)
-		if secured {
-			authority = AppendNSEC(authority, record.Zone, qname, secured)
-			res = dns.RcodeSuccess
-		}
+		authority = append(authority, originalRecord.Zone.Config.SOA.Data)
 	} else if localRes == dns.RcodeNotAuth {
 		if h.Config.UpstreamFallback {
 			upstreamAnswers, upstreamRes := h.upstream.Query(dns.Fqdn(qname), qtype)
@@ -263,12 +266,27 @@ func (h *DnsRequestHandler) HandleRequest(state *request.Request) {
 			res = upstreamRes
 		} else if originalRecord != nil && originalRecord.CNAME != nil {
 			if len(answers) == 0 {
-				answers = AppendRR(answers, h.CNAME(qname, record), qname, record, secured)
+				answers = append(answers, h.CNAME(qname, originalRecord)...)
+			}
+			res = dns.RcodeSuccess
+		}
+	}
+
+	if 	auth && state.Do() && originalRecord != nil && originalRecord.Zone.Config.DnsSec {
+		switch res {
+		case dns.RcodeSuccess:
+			if len(answers) == 0 {
+				authority = append(authority, NSec(qname, originalRecord.Zone))
 			}
+		case dns.RcodeNameError:
+			authority = append(authority, NSec(qname, originalRecord.Zone))
 			res = dns.RcodeSuccess
 		}
+		answers = Sign(answers, qname, originalRecord)
+		authority = Sign(authority, qname, originalRecord)
 	}
 
+
 	h.LogRequest(logData, requestStartTime, res)
 	m := new(dns.Msg)
 	m.SetReply(state.Req)
@@ -311,8 +329,8 @@ func (h *DnsRequestHandler) Filter(request *request.Request, rrset *IP_RRSet, lo
 		default:
 			index = 0
 		}
-		logData["DestinationIp"] = ips[index].Ip.String()
-		logData["DestinationCountry"] = ips[index].Country
+		logData["destination_ip"] = ips[index].Ip.String()
+		logData["destination_country"] = ips[index].Country
 		return []IP_RR{ips[index]}
 
 	case "multi":
@@ -332,9 +350,10 @@ func (h *DnsRequestHandler) Filter(request *request.Request, rrset *IP_RRSet, lo
 }
 
 func (h *DnsRequestHandler) LogRequest(data map[string]interface{}, startTime time.Time, responseCode int) {
-	data["ProcessTime"] = time.Since(startTime).Nanoseconds() / 1000000
-	data["ResponseCode"] = responseCode
-	h.Logger.Log(data, "ar_dns_request")
+	data["process_time"] = time.Since(startTime).Nanoseconds() / 1000000
+	data["response_code"] = responseCode
+	data["log_type"] = "request"
+	h.Logger.Log(data, "dns request")
 }
 
 func GetSourceIp(request *request.Request) net.IP {
@@ -366,10 +385,9 @@ func GetSourceSubnet(request *request.Request) string {
 func reverseZone(zone string) string {
 	x := strings.Split(zone, ".")
 	var y string
-	for i := len(x) - 1; i > 0; i-- {
+	for i := len(x) - 1; i >= 0; i-- {
 		y += x[i] + "."
 	}
-	y += x[0]
 	return y
 }
 
@@ -390,10 +408,10 @@ func (h *DnsRequestHandler) FetchRecord(qname string, logData map[string]interfa
 	cachedRecord, found := h.RecordCache.Get(qname)
 	if found {
 		logger.Default.Debug("cached")
-		logData["Cache"] = "HIT"
+		logData["cache"] = "HIT"
 		return cachedRecord.(*Record), dns.RcodeSuccess
 	} else {
-		logData["Cache"] = "MISS"
+		logData["cache"] = "MISS"
 		record, res := h.GetRecord(qname)
 		if res == dns.RcodeSuccess {
 			h.RecordCache.Set(qname, record, time.Duration(h.Config.CacheTimeout)*time.Second)
@@ -645,9 +663,8 @@ func split255(s string) []string {
 }
 
 func (h *DnsRequestHandler) Matches(qname string) string {
-	rzname := reverseZone(qname)
-	_, zname, ok := h.Zones.Root().LongestPrefix([]byte(rzname))
-	if ok {
+	rname := reverseZone(qname)
+	if _, zname, ok := h.Zones.Root().LongestPrefix([]byte(rname)); ok {
 		return zname.(string)
 	}
 	return ""
@@ -671,7 +688,6 @@ func (h *DnsRequestHandler) GetRecord(qname string) (record *Record, rcode int)
 
 	location := h.findLocation(qname, z)
 	if len(location) == 0 { // empty, no results
-		logger.Default.Errorf("location not exists : %s", qname)
 		return &Record{Name: qname, Zone: z}, dns.RcodeNameError
 	}
 	logger.Default.Debugf("location : %s", location)
@@ -684,6 +700,37 @@ func (h *DnsRequestHandler) GetRecord(qname string) (record *Record, rcode int)
 	return record, dns.RcodeSuccess
 }
 
+func (h *DnsRequestHandler) loadKey(pub string, priv string) *ZoneKey {
+	pubStr, _ := h.Redis.Get(pub)
+	if pubStr == "" {
+		logger.Default.Errorf("key is not set : %s", pub)
+		return nil
+	}
+	privStr, _ := h.Redis.Get(priv)
+	if privStr == "" {
+		logger.Default.Errorf("key is not set : %s", priv)
+		return nil
+	}
+	privStr = strings.Replace(privStr, "\\n", "\n", -1)
+	zoneKey := new(ZoneKey)
+	if rr, err := dns.NewRR(pubStr); err == nil {
+		zoneKey.DnsKey = rr.(*dns.DNSKEY)
+	} else {
+		logger.Default.Errorf("cannot parse zone key : %s", err)
+		return nil
+	}
+	if pk, err := zoneKey.DnsKey.NewPrivateKey(privStr); err == nil {
+		zoneKey.PrivateKey = pk
+	} else {
+		logger.Default.Errorf("cannot create private key : %s", err)
+		return nil
+	}
+	now := time.Now()
+	zoneKey.KeyInception = uint32(now.Add(-3 * time.Hour).Unix())
+	zoneKey.KeyExpiration = uint32(now.Add(8 * 24 * time.Hour).Unix())
+	return zoneKey
+}
+
 func (h *DnsRequestHandler) LoadZone(zone string) *Zone {
 	cachedZone, found := h.ZoneCache.Get(zone)
 	if found {
@@ -739,32 +786,24 @@ func (h *DnsRequestHandler) LoadZone(zone string) *Zone {
 
 	z = func() *Zone {
 		if z.Config.DnsSec {
-			pubStr, _ := h.Redis.Get("redins:zones:" + z.Name + ":pub")
-			privStr, _ := h.Redis.Get("redins:zones:" + z.Name + ":priv")
-			privStr = strings.Replace(privStr, "\\n", "\n", -1)
-			if pubStr == "" || privStr == "" {
-				logger.Default.Errorf("key is not set for zone %s", z.Name)
+			z.ZSK = h.loadKey("redins:zones:" + z.Name + ":zsk:pub", "redins:zones:" + z.Name + ":zsk:priv")
+			if z.ZSK == nil {
 				z.Config.DnsSec = false
 				return z
 			}
-			if rr, err := dns.NewRR(pubStr); err == nil {
-				z.DnsKey = rr.(*dns.DNSKEY)
-			} else {
-				logger.Default.Errorf("cannot parse zone key : %s", err)
+			z.KSK = h.loadKey("redins:zones:" + z.Name + ":ksk:pub", "redins:zones:" + z.Name + ":ksk:priv")
+			if z.KSK == nil {
 				z.Config.DnsSec = false
 				return z
 			}
-			if pk, err := z.DnsKey.NewPrivateKey(privStr); err == nil {
-				z.PrivateKey = pk
-			} else {
-				logger.Default.Errorf("cannot create private key : %s", err)
-				z.Config.DnsSec = false
-				return z
+
+			z.ZSK.DnsKey.Flags = 256
+			z.KSK.DnsKey.Flags = 257
+			if z.ZSK.DnsKey.Hdr.Ttl != z.KSK.DnsKey.Hdr.Ttl {
+				z.ZSK.DnsKey.Hdr.Ttl = z.KSK.DnsKey.Hdr.Ttl
 			}
-			now := time.Now()
-			z.KeyInception = uint32(now.Add(-3 * time.Hour).Unix())
-			z.KeyExpiration = uint32(now.Add(8 * 24 * time.Hour).Unix())
-			if rrsig, err := Sign([]dns.RR{z.DnsKey}, z.Name, z, 300); err == nil {
+
+			if rrsig, err := sign([]dns.RR{z.ZSK.DnsKey, z.KSK.DnsKey}, z.Name, z.KSK, z.KSK.DnsKey.Hdr.Ttl); err == nil {
 				z.DnsKeySig = rrsig
 			} else {
 				logger.Default.Errorf("cannot create RRSIG for DNSKEY : %s", err)
@@ -863,39 +902,6 @@ func ChooseIp(ips []IP_RR, weighted bool) int {
 	return index
 }
 
-func AppendRR(answers []dns.RR, rrs []dns.RR, qname string, record *Record, secured bool) []dns.RR {
-	if len(rrs) == 0 {
-		return answers
-	}
-	answers = append(answers, rrs...)
-	if secured {
-		if rrsig, err := Sign(rrs, qname, record.Zone, rrs[0].Header().Ttl); err == nil {
-			answers = append(answers, rrsig)
-		}
-	}
-	return answers
-}
-
-func AppendSOA(target []dns.RR, zone *Zone, secured bool) []dns.RR {
-	target = append(target, zone.Config.SOA.Data)
-	if secured {
-		if rrsig, err := Sign([]dns.RR{zone.Config.SOA.Data}, zone.Name, zone, zone.Config.SOA.Ttl); err == nil {
-			target = append(target, rrsig)
-		}
-	}
-	return target
-}
-
-func AppendNSEC(target []dns.RR, zone *Zone, qname string, secured bool) []dns.RR {
-	if !secured {
-		return target
-	}
-	if nsec, err := NSec(qname, zone); err == nil {
-		target = append(target, nsec...)
-	}
-	return target
-}
-
 func (h *DnsRequestHandler) FindCAA(record *Record) *Record {
 	zone := record.Zone
 	currentRecord := record
diff --git a/handler/handler_test.go b/handler/handler_test.go
index 35d78d2ff195480e665b8019840d3e4a71f6cdb1..e5390103fd023995437524555ee485e245323907 100644
--- a/handler/handler_test.go
+++ b/handler/handler_test.go
@@ -1538,18 +1538,211 @@ func TestUpstreamCNAME(t *testing.T) {
 			log.Println("3")
 			t.Fail()
 		}
-		hasCNAME := false
-		hasA := false
-		for _, rr := range resp.Answer {
-			switch rr.(type) {
-			case *dns.CNAME:
-				hasCNAME = true
-			case *dns.A:
-				hasA = true
+		cname := resp.Answer[0].(*dns.CNAME)
+		if cname.Target != "www.google.com." {
+			log.Println("4 ", cname)
+			t.Fail()
+		}
+	}
+}
+
+var cnameOutsideZones = []string{"inside.cnm.", "outside.cnm."}
+var cnameOutsideConfig = []string{
+	`{"soa":{"ttl":300, "minttl":100, "mbox":"hostmaster.inside.cnm.","ns":"ns1.inside.cnm.","refresh":44,"retry":55,"expire":66}}`,
+	`{"soa":{"ttl":300, "minttl":100, "mbox":"hostmaster.outside.cnm.","ns":"ns1.outside.cnm.","refresh":44,"retry":55,"expire":66}}`,
+}
+var cnameOutsideEntries = [][][]string{
+	{
+		{"upstream",
+			`{"cname":{"ttl":300, "host":"outside.cnm."}}`,
+		},
+	},
+	{
+		{"@",
+			`{"a":{"ttl":300, "records":[{"ip":"127.0.0.6"}]}}`,
+		},
+	},
+}
+
+var cnameOutsideTests = []test.Case{
+	{
+		Qname: "upstream.inside.cnm.", Qtype: dns.TypeA,
+		Answer: []dns.RR{
+			test.CNAME("upstream.inside.cnm. 300 IN CNAME outside.cnm."),
+		},
+	},
+}
+
+func TestCNameOutsideZone(t *testing.T) {
+	logger.Default = logger.NewLogger(&logger.LogConfig{})
+
+	h := NewHandler(&handlerTestConfig)
+	h.Redis.Del("*")
+	for i, zone := range cnameOutsideZones {
+		h.Redis.SAdd("redins:zones", zone)
+		for _, cmd := range cnameOutsideEntries[i] {
+			err := h.Redis.HSet("redins:zones:"+zone, cmd[0], cmd[1])
+			if err != nil {
+				log.Printf("[ERROR] cannot connect to redis: %s", err)
+				t.Fail()
 			}
 		}
-		if !hasCNAME || !hasA {
-			log.Println("4")
+	}
+	h.LoadZones()
+	for j, tc := range cnameOutsideTests {
+
+		r := tc.Msg()
+		w := test.NewRecorder(&test.ResponseWriter{})
+		state := request.Request{W: w, Req: r}
+		h.HandleRequest(&state)
+
+		resp := w.Msg
+
+		fmt.Println(j, tc.Qname, tc.Answer, resp.Answer)
+		if err := test.SortAndCheck(resp, tc); err != nil {
+			t.Fail()
+			fmt.Println(err)
+		}
+	}
+}
+
+var cnameLoopZone = "loop.cnm."
+
+var cnameLoopEntries = [][]string{
+	{"w1",
+		`{"cname":{"ttl":300, "host":"w2.loop.cnm."}}`,
+	},
+	{"w2",
+		`{"cname":{"ttl":300, "host":"w1.loop.cnm."}}`,
+	},
+}
+
+var cnameLoopTests = []test.Case{
+	{
+		Qname: "w1.loop.cnm.", Qtype: dns.TypeA,
+		Rcode: dns.RcodeServerFailure,
+	},
+}
+
+func TestCNameLoop(t *testing.T) {
+	logger.Default = logger.NewLogger(&logger.LogConfig{})
+
+	h := NewHandler(&handlerTestConfig)
+	h.Redis.Del("*")
+	h.Redis.SAdd("redins:zones", cnameLoopZone)
+	for _, cmd := range cnameLoopEntries {
+		err := h.Redis.HSet("redins:zones:"+cnameLoopZone, cmd[0], cmd[1])
+		if err != nil {
+			log.Printf("[ERROR] cannot connect to redis: %s", err)
+			t.Fail()
+		}
+	}
+
+	h.LoadZones()
+	for j, tc := range cnameLoopTests {
+
+		r := tc.Msg()
+		w := test.NewRecorder(&test.ResponseWriter{})
+		state := request.Request{W: w, Req: r}
+		h.HandleRequest(&state)
+
+		resp := w.Msg
+
+		fmt.Println(j, tc.Qname, tc.Answer, resp.Answer)
+		if err := test.SortAndCheck(resp, tc); err != nil {
+			t.Fail()
+			fmt.Println(err)
+		}
+	}
+}
+
+var findZoneZones = []string{"zone.zon.", "sub1.zone.zon."}
+
+var findZoneEntries = [][][]string{
+	{
+		{"sub3.sub2",
+			`{"a":{"ttl":300, "records":[{"ip":"1.1.1.1"}]}}`,
+		},
+		{"sub1",
+			`{"a":{"ttl":300, "records":[{"ip":"2.2.2.2"}]}}`,
+		},
+		{"sub10",
+			`{"a":{"ttl":300, "records":[{"ip":"5.5.5.5"}]}}`,
+		},
+		{"ub1",
+			`{"a":{"ttl":300, "records":[{"ip":"6.6.6.6"}]}}`,
+		},
+	},
+	{
+		{"@",
+			`{"a":{"ttl":300, "records":[{"ip":"3.3.3.3"}]}}`,
+		},
+		{"sub2",
+			`{"a":{"ttl":300, "records":[{"ip":"4.4.4.4"}]}}`,
+		},
+	},
+}
+
+var findZoneTests = []test.Case{
+	{
+		Qname: "sub1.zone.zon.", Qtype: dns.TypeA,
+		Answer: []dns.RR{
+			test.A("sub1.zone.zon. 300 IN A 3.3.3.3"),
+		},
+	},
+	{
+		Qname: "sub10.zone.zon.", Qtype: dns.TypeA,
+		Answer: []dns.RR{
+			test.A("sub10.zone.zon. 300 IN A 5.5.5.5"),
+		},
+	},
+	{
+		Qname: "sub3.sub2.zone.zon.", Qtype: dns.TypeA,
+		Answer: []dns.RR{
+			test.A("sub3.sub2.zone.zon. 300 IN A 1.1.1.1"),
+		},
+	},
+	{
+		Qname: "sub2.sub1.zone.zon.", Qtype: dns.TypeA,
+		Answer: []dns.RR{
+			test.A("sub2.sub1.zone.zon. 300 IN A 4.4.4.4"),
+		},
+	},
+	{
+		Qname: "ub1.zone.zon.", Qtype: dns.TypeA,
+		Answer: []dns.RR{
+			test.A("ub1.zone.zon. 300 IN A 6.6.6.6"),
+		},
+	},
+}
+
+func TestFindZone(t *testing.T) {
+	logger.Default = logger.NewLogger(&logger.LogConfig{})
+
+	h := NewHandler(&handlerTestConfig)
+	h.Redis.Del("*")
+	for i, zone := range findZoneZones {
+		h.Redis.SAdd("redins:zones", zone)
+		for _, cmd := range findZoneEntries[i] {
+			err := h.Redis.HSet("redins:zones:"+zone, cmd[0], cmd[1])
+			if err != nil {
+				log.Printf("[ERROR] cannot connect to redis: %s", err)
+				t.Fail()
+			}
+		}
+		h.Redis.Set("redins:zones:"+zone+":config", lookupConfig[i])
+		h.LoadZones()
+	}
+	for _, tc := range findZoneTests {
+		r := tc.Msg()
+		w := test.NewRecorder(&test.ResponseWriter{})
+		state := request.Request{W: w, Req: r}
+		h.HandleRequest(&state)
+
+		resp := w.Msg
+
+		if err := test.SortAndCheck(resp, tc); err != nil {
+			fmt.Println(tc.Qname, tc.Answer, resp.Answer)
 			t.Fail()
 		}
 	}
@@ -1605,6 +1798,10 @@ func TestSubscribeZones(t *testing.T) {
 		},
 	}
 
+	rd := uperdis.NewRedis(&handlerTestConfig.Redis)
+	rd.SetConfig("notify-keyspace-events", "AK")
+	time.Sleep(time.Second)
+
 	h := NewHandler(&handlerTestConfig)
 	h.Redis.Del("*")
 	for _, cmd := range subsEntries {
@@ -1644,3 +1841,58 @@ func TestSubscribeZones(t *testing.T) {
 		t.Fail()
 	}
 }
+
+var cnameNoAuthZone = "auth.zon."
+
+var cnameNoAuthEntries = [][]string{
+	{"w1",
+		`{"cname":{"ttl":300, "host":"w2.auth.zon."}}`,
+	},
+	{"w2",
+		`{"cname":{"ttl":300, "host":"noauth.zon."}}`,
+	},
+}
+
+var cnameNoAuthTests = []test.Case{
+	{
+		Qname: "w1.auth.zon.", Qtype: dns.TypeA,
+		Answer: []dns.RR {
+		    test.CNAME("w1.auth.zon.	300	IN	CNAME	w2.auth.zon."),
+		    test.CNAME("w2.auth.zon.	300	IN	CNAME	noauth.zon."),
+        },
+		Rcode: dns.RcodeSuccess,
+	},
+}
+
+func TestCNameNoAuth(t *testing.T) {
+	logger.Default = logger.NewLogger(&logger.LogConfig{})
+
+	h := NewHandler(&handlerTestConfig)
+	h.Redis.Del("*")
+	h.Redis.SAdd("redins:zones", cnameNoAuthZone)
+	h.Redis.Set("redins:zones:" + cnameNoAuthZone + ":config", "{\"cname_flattening\": false}")
+	for _, cmd := range cnameNoAuthEntries {
+		err := h.Redis.HSet("redins:zones:"+cnameNoAuthZone, cmd[0], cmd[1])
+		if err != nil {
+			log.Printf("[ERROR] cannot connect to redis: %s", err)
+			t.Fail()
+		}
+	}
+
+	h.LoadZones()
+	for j, tc := range cnameNoAuthTests {
+
+		r := tc.Msg()
+		w := test.NewRecorder(&test.ResponseWriter{})
+		state := request.Request{W: w, Req: r}
+		h.HandleRequest(&state)
+
+		resp := w.Msg
+
+		fmt.Println(j, tc.Qname, tc.Answer, resp.Answer)
+		if err := test.SortAndCheck(resp, tc); err != nil {
+			t.Fail()
+			fmt.Println(err)
+		}
+	}
+}
diff --git a/handler/healthcheck.go b/handler/healthcheck.go
index 936da73bca756b2bf149b818763d26e4a81f0143..0b461c0871c6ff2a3cd32b33c97f453a911dea28 100644
--- a/handler/healthcheck.go
+++ b/handler/healthcheck.go
@@ -30,7 +30,7 @@ type HealthCheckItem struct {
 	UpCount   int       `json:"up_count,omitempty"`
 	DownCount int       `json:"down_count,omitempty"`
 	Enable    bool      `json:"enable,omitempty"`
-	DomainId  string    `json:"domain_uuid,omitempty"`
+	DomainId  string    `json:"domain_uuid, omitempty"`
 	Host      string    `json:"-"`
 	Ip        string    `json:"-"`
 	Error     error     `json:"-"`
@@ -309,10 +309,11 @@ func (h *Healthcheck) logHealthcheck(item *HealthCheckItem) {
 	data := map[string]interface{}{
 		"ip":          item.Ip,
 		"port":        item.Port,
-		"domain_name": item.Host,
+		"host":        item.Host,
 		"domain_uuid": item.DomainId,
 		"uri":         item.Uri,
 		"status":      item.Status,
+		"log_type":    "healthcheck",
 	}
 	if item.Error == nil {
 		data["error"] = ""
@@ -320,7 +321,7 @@ func (h *Healthcheck) logHealthcheck(item *HealthCheckItem) {
 		data["error"] = item.Error.Error()
 	}
 
-	h.logger.Log(data, "ar_dns_healthcheck")
+	h.logger.Log(data, "dns healthcheck")
 }
 
 func statusDown(item *HealthCheckItem) {
diff --git a/handler/server.go b/handler/server.go
index 74419f52d50464648d06becb060b8fb00dc6ede6..9ed50e3c1cf3e019ee583402263e01b1473a98e2 100644
--- a/handler/server.go
+++ b/handler/server.go
@@ -4,12 +4,52 @@ import (
 	"strconv"
 
 	"github.com/miekg/dns"
+	"crypto/tls"
+	"crypto/x509"
+	"io/ioutil"
 )
 
+type TlsConfig struct {
+	Enable   bool `json:"enable"`
+	CertPath string `json:"cert_path"`
+	KeyPath  string `json:"key_path"`
+	CaPath   string `json:"ca_path"`
+}
+
 type ServerConfig struct {
 	Ip       string `json:"ip,omitempty"`
 	Port     int    `json:"port,omitempty"`
 	Protocol string `json:"protocol,omitempty"`
+	Tls      TlsConfig `json:"tls,omitempty"`
+}
+
+func loadRoots(caPath string) *x509.CertPool {
+	if caPath == "" {
+		return nil
+	}
+
+	roots := x509.NewCertPool()
+	pem, err := ioutil.ReadFile(caPath)
+	if err != nil {
+		return nil
+	}
+	ok := roots.AppendCertsFromPEM(pem)
+	if !ok {
+		return nil
+	}
+	return roots
+}
+
+func loadTlsConfig(cfg TlsConfig) *tls.Config {
+	root := loadRoots(cfg.CaPath)
+	if cfg.KeyPath == "" || cfg.CertPath == "" {
+		return &tls.Config{RootCAs: root}
+	}
+	cert, err := tls.LoadX509KeyPair(cfg.CertPath, cfg.KeyPath)
+	if err != nil {
+		return nil
+	}
+	return &tls.Config{Certificates: []tls.Certificate{cert}, RootCAs: root}
 }
 
 func NewServer(config []ServerConfig) []dns.Server {
@@ -19,6 +59,9 @@ func NewServer(config []ServerConfig) []dns.Server {
 			Addr: cfg.Ip + ":" + strconv.Itoa(cfg.Port),
 			Net:  cfg.Protocol,
 		}
+		if cfg.Tls.Enable {
+			server.TLSConfig = loadTlsConfig(cfg.Tls)
+		}
 		servers = append(servers, server)
 	}
 	return servers