diff --git a/assets/tpl/admin_edit_interface.html b/assets/tpl/admin_edit_interface.html
index c1a8c6e..dc1cf39 100644
--- a/assets/tpl/admin_edit_interface.html
+++ b/assets/tpl/admin_edit_interface.html
@@ -264,7 +264,7 @@
                     
                     
                 
+                
                     
                     
                 
diff --git a/go.mod b/go.mod
index 27c93e4..657a6aa 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,7 @@ require (
 	github.com/gin-contrib/sessions v0.0.3
 	github.com/gin-gonic/gin v1.6.3
 	github.com/go-ldap/ldap/v3 v3.2.4
-	github.com/go-playground/validator/v10 v10.2.0
+	github.com/go-playground/validator/v10 v10.4.1
 	github.com/gorilla/sessions v1.2.1 // indirect
 	github.com/jordan-wright/email v4.0.1-0.20200917010138-e1c00e156980+incompatible
 	github.com/kelseyhightower/envconfig v1.4.0
@@ -17,7 +17,7 @@ require (
 	github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e
 	github.com/toorop/gin-logrus v0.0.0-20200831135515-d2ee50d38dae
 	github.com/utrack/gin-csrf v0.0.0-20190424104817-40fb8d2c8fca
-	golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9
+	golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200609130330-bd2cb7843e1b
 	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
 	gorm.io/driver/mysql v1.0.4
diff --git a/internal/wireguard/peermanager.go b/internal/wireguard/peermanager.go
index 33a454e..57e6dc2 100644
--- a/internal/wireguard/peermanager.go
+++ b/internal/wireguard/peermanager.go
@@ -7,13 +7,13 @@ import (
 	"crypto/md5"
 	"fmt"
 	"net"
-	"reflect"
 	"regexp"
 	"sort"
 	"strings"
-	"text/template"
 	"time"
 
+	"github.com/gin-gonic/gin"
+
 	"github.com/gin-gonic/gin/binding"
 	"github.com/go-playground/validator/v10"
 	"github.com/h44z/wg-portal/internal/common"
@@ -42,7 +42,6 @@ var cidrList validator.Func = func(fl validator.FieldLevel) bool {
 
 var ipList validator.Func = func(fl validator.FieldLevel) bool {
 	ipListStr := fl.Field().String()
-
 	ipList := common.ParseStringList(ipListStr)
 	for i := range ipList {
 		ip := net.ParseIP(ipList[i])
@@ -65,15 +64,16 @@ func init() {
 //
 
 type Peer struct {
-	Peer   *wgtypes.Peer `gorm:"-"`                     // WireGuard peer
-	Device *Device       `gorm:"foreignKey:DeviceName"` // linked WireGuard device
+	Peer   *wgtypes.Peer `gorm:"-"`                                 // WireGuard peer
+	Device *Device       `gorm:"foreignKey:DeviceName" binding:"-"` // linked WireGuard device
 	Config string        `gorm:"-"`
 
-	UID                  string `form:"uid" binding:"alphanum"` // uid for html identification
-	DeviceName           string `gorm:"index"`
-	Identifier           string `form:"identifier" binding:"required,max=64"` // Identifier AND Email make a WireGuard peer unique
-	Email                string `gorm:"index" form:"mail" binding:"omitempty,email"`
-	IgnoreGlobalSettings bool   `form:"ignoreglobalsettings"`
+	UID                  string     `form:"uid" binding:"required,alphanum"` // uid for html identification
+	DeviceName           string     `gorm:"index" form:"device" binding:"required"`
+	DeviceType           DeviceType `gorm:"-" form:"devicetype" binding:"required,oneof=client server custom"`
+	Identifier           string     `form:"identifier" binding:"required,max=64"` // Identifier AND Email make a WireGuard peer unique
+	Email                string     `gorm:"index" form:"mail" binding:"required,email"`
+	IgnoreGlobalSettings bool       `form:"ignoreglobalsettings"`
 
 	IsOnline          bool   `gorm:"-"`
 	IsNew             bool   `gorm:"-"`
@@ -81,16 +81,19 @@ type Peer struct {
 	LastHandshakeTime string `gorm:"-"`
 
 	// Core WireGuard Settings
-	PublicKey           string `gorm:"primaryKey" form:"pubkey" binding:"required,base64"`
+	PublicKey           string `gorm:"primaryKey" form:"pubkey" binding:"required,base64"` // the public key of the peer itself
 	PresharedKey        string `form:"presharedkey" binding:"omitempty,base64"`
 	AllowedIPsStr       string `form:"allowedip" binding:"cidrlist"` // a comma separated list of IPs that are used in the client config file
 	Endpoint            string `form:"endpoint" binding:"omitempty,hostname_port"`
 	PersistentKeepalive int    `form:"keepalive" binding:"gte=0"`
 
 	// Misc. WireGuard Settings
-	PrivateKey string `form:"privkey" binding:"omitempty,base64"`
-	IPsStr     string `form:"ip" binding:"cidrlist"` // a comma separated list of IPs of the client
-	DNSStr     string `form:"dns" binding:"iplist"`  // comma separated list of the DNS servers for the client
+	EndpointPublicKey string `form:"endpointpubkey" binding:"required,base64"` // the public key of the remote endpoint
+	PrivateKey        string `form:"privkey" binding:"omitempty,base64"`
+	IPsStr            string `form:"ip" binding:"cidrlist,required_if=devicetype server"` // a comma separated list of IPs of the client
+	DNSStr            string `form:"dns" binding:"iplist"`                                // comma separated list of the DNS servers for the client
+	// Global Device Settings (can be ignored, only make sense if device is in server mode)
+	Mtu int `form:"mtu" binding:"gte=0,lte=1500"`
 
 	DeactivatedAt *time.Time
 	CreatedBy     string
@@ -131,40 +134,49 @@ func (p Peer) GetConfig() wgtypes.PeerConfig {
 		presharedKey = &presharedKeyTmp
 	}
 
+	var endpoint *net.UDPAddr
+	if p.Endpoint != "" {
+		addr, err := net.ResolveUDPAddr("udp", p.Endpoint)
+		if err == nil {
+			endpoint = addr
+		}
+	}
+
+	var keepAlive *time.Duration
+	if p.PersistentKeepalive != 0 {
+		keepAliveDuration := time.Duration(p.PersistentKeepalive) * time.Second
+		keepAlive = &keepAliveDuration
+	}
+
+	peerAllowedIPs := p.GetAllowedIPs()
+	allowedIPs := make([]net.IPNet, len(peerAllowedIPs))
+	for i, ip := range peerAllowedIPs {
+		_, ipNet, err := net.ParseCIDR(ip)
+		if err == nil {
+			allowedIPs[i] = *ipNet
+		}
+	}
+
 	cfg := wgtypes.PeerConfig{
 		PublicKey:                   publicKey,
 		Remove:                      false,
 		UpdateOnly:                  false,
 		PresharedKey:                presharedKey,
-		Endpoint:                    nil,
-		PersistentKeepaliveInterval: nil,
+		Endpoint:                    endpoint,
+		PersistentKeepaliveInterval: keepAlive,
 		ReplaceAllowedIPs:           true,
-		AllowedIPs:                  make([]net.IPNet, len(p.GetIPAddresses())),
-	}
-	for i, ip := range p.GetIPAddresses() {
-		_, ipNet, err := net.ParseCIDR(ip)
-		if err == nil {
-			cfg.AllowedIPs[i] = *ipNet
-		}
+		AllowedIPs:                  allowedIPs,
 	}
 
 	return cfg
 }
 
 func (p Peer) GetConfigFile(device Device) ([]byte, error) {
-	tpl, err := template.New("client").Funcs(template.FuncMap{"StringsJoin": strings.Join}).Parse(ClientCfgTpl)
-	if err != nil {
-		return nil, errors.Wrap(err, "failed to parse client template")
-	}
-
 	var tplBuff bytes.Buffer
 
-	err = tpl.Execute(&tplBuff, struct {
-		Client Peer
-		Server Device
-	}{
-		Client: p,
-		Server: device,
+	err := templateCache.ExecuteTemplate(&tplBuff, "peer.tpl", gin.H{
+		"Peer":      p,
+		"Interface": device,
 	})
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to execute client template")
@@ -192,26 +204,6 @@ func (p Peer) IsValid() bool {
 	return true
 }
 
-func (p Peer) ToMap() map[string]string {
-	out := make(map[string]string)
-
-	v := reflect.ValueOf(p)
-	if v.Kind() == reflect.Ptr {
-		v = v.Elem()
-	}
-
-	typ := v.Type()
-	for i := 0; i < v.NumField(); i++ {
-		// gets us a StructField
-		fi := typ.Field(i)
-		if tagv := fi.Tag.Get("form"); tagv != "" {
-			// set key of map to value in struct field
-			out[tagv] = v.Field(i).String()
-		}
-	}
-	return out
-}
-
 func (p Peer) GetConfigFileName() string {
 	reg := regexp.MustCompile("[^a-zA-Z0-9_-]+")
 	return reg.ReplaceAllString(strings.ReplaceAll(p.Identifier, " ", "-"), "") + ".conf"
@@ -238,7 +230,7 @@ type Device struct {
 
 	// Core WireGuard Settings (Interface section)
 	PrivateKey   string `form:"privkey" binding:"required,base64"`
-	ListenPort   int    `form:"port" binding:"gt=0,lt=65535"`
+	ListenPort   int    `form:"port" binding:"omitempty,gt=0,lt=65535,required_if=devicetype server"`
 	FirewallMark int32  `form:"firewallmark" binding:"gte=0"`
 	// Misc. WireGuard Settings
 	PublicKey    string `form:"pubkey" binding:"required,base64"`
@@ -253,7 +245,7 @@ type Device struct {
 	SaveConfig   bool   `form:"saveconfig"`                     // if set to `true', the configuration is saved from the current state of the interface upon shutdown, wg-quick addition
 
 	// Settings that are applied to all peer by default
-	DefaultEndpoint            string `form:"endpoint" binding:"omitempty,hostname_port"`
+	DefaultEndpoint            string `form:"endpoint" binding:"omitempty,hostname_port,required_if=devicetype server"`
 	DefaultAllowedIPsStr       string `form:"allowedip" binding:"cidrlist"` // comma separated list  of IPs that are used in the client config file
 	DefaultPersistentKeepalive int    `form:"keepalive" binding:"gte=0"`
 
@@ -317,28 +309,23 @@ func (d Device) GetConfig() wgtypes.Config {
 		privateKey = &pKey
 	}
 
+	fwMark := int(d.FirewallMark)
+
 	cfg := wgtypes.Config{
-		PrivateKey: privateKey,
-		ListenPort: &d.ListenPort,
+		PrivateKey:   privateKey,
+		ListenPort:   &d.ListenPort,
+		FirewallMark: &fwMark,
 	}
 
 	return cfg
 }
 
 func (d Device) GetConfigFile(peers []Peer) ([]byte, error) {
-	tpl, err := template.New("server").Funcs(template.FuncMap{"StringsJoin": strings.Join}).Parse(DeviceCfgTpl)
-	if err != nil {
-		return nil, errors.Wrap(err, "failed to parse server template")
-	}
-
 	var tplBuff bytes.Buffer
 
-	err = tpl.Execute(&tplBuff, struct {
-		Clients []Peer
-		Server  Device
-	}{
-		Clients: peers,
-		Server:  d,
+	err := templateCache.ExecuteTemplate(&tplBuff, "interface.tpl", gin.H{
+		"Peers":     peers,
+		"Interface": d,
 	})
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to execute server template")
diff --git a/internal/wireguard/template.go b/internal/wireguard/template.go
index 3b65e57..99cdb1c 100644
--- a/internal/wireguard/template.go
+++ b/internal/wireguard/template.go
@@ -1,53 +1,20 @@
 package wireguard
 
-var (
-	ClientCfgTpl = `#{{ .Client.Identifier }}
-[Interface]
-Address = {{ .Client.IPsStr }}
-PrivateKey = {{ .Client.PrivateKey }}
-{{- if .Server.DNSStr}}
-DNS = {{ .Server.DNSStr }}
-{{- end}}
-{{- if ne .Server.Mtu 0}}
-MTU = {{.Server.Mtu}}
-{{- end}}
-
-[Peer]
-PublicKey = {{ .Server.PublicKey }}
-{{- if .Client.PresharedKey}}
-PresharedKey = {{ .Client.PresharedKey }}
-{{- end}}
-AllowedIPs = {{ .Client.AllowedIPsStr }}
-Endpoint = {{ .Server.Endpoint }}
-{{- if and (ne .Server.PersistentKeepalive 0) (not .Client.IgnorePersistentKeepalive)}}
-PersistentKeepalive = {{.Server.PersistentKeepalive}}
-{{- end}}
-`
-	DeviceCfgTpl = `# AUTOGENERATED FILE - DO NOT EDIT
-# Updated: {{ .Server.UpdatedAt }} / Created: {{ .Server.CreatedAt }}
-[Interface]
-{{- range .Server.IPs}}
-Address = {{ . }}
-{{- end}}
-ListenPort = {{ .Server.ListenPort }}
-PrivateKey = {{ .Server.PrivateKey }}
-{{- if ne .Server.Mtu 0}}
-MTU = {{.Server.Mtu}}
-{{- end}}
-PreUp = {{ .Server.PreUp }}
-PostUp = {{ .Server.PostUp }}
-PreDown = {{ .Server.PreDown }}
-PostDown = {{ .Server.PostDown }}
-
-{{range .Clients}}
-{{if not .DeactivatedAt -}}
-# {{.Identifier}} / {{.Email}} / Updated: {{.UpdatedAt}} / Created: {{.CreatedAt}}
-[Peer]
-PublicKey = {{ .PublicKey }}
-{{- if .PresharedKey}}
-PresharedKey = {{ .PresharedKey }}
-{{- end}}
-AllowedIPs = {{ StringsJoin .IPs ", " }}
-{{- end}}
-{{end}}`
+import (
+	"embed"
+	"html/template"
+	"strings"
 )
+
+//go:embed tpl/*
+var Templates embed.FS
+
+var templateCache *template.Template
+
+func init() {
+	var err error
+	templateCache, err = template.New("server").Funcs(template.FuncMap{"StringsJoin": strings.Join}).ParseFS(Templates, "tpl/*.tpl")
+	if err != nil {
+		panic(err)
+	}
+}
diff --git a/internal/wireguard/tpl/interface.tpl b/internal/wireguard/tpl/interface.tpl
new file mode 100644
index 0000000..8540c2a
--- /dev/null
+++ b/internal/wireguard/tpl/interface.tpl
@@ -0,0 +1,58 @@
+# AUTOGENERATED FILE - DO NOT EDIT
+# -WGP- Interface: {{ .Interface.DeviceName }} / Updated: {{ .Interface.UpdatedAt }} / Created: {{ .Interface.CreatedAt }}
+# -WGP- Interface display name: {{ .Interface.DisplayName }}
+# -WGP- Interface mode: {{ .Interface.Type }}
+# -WGP- PublicKey = {{ .Interface.PublicKey }}
+
+[Interface]
+
+# Core settings
+PrivateKey = {{ .Interface.PrivateKey }}
+Address = {{ .Interface.IPsStr }}
+
+# Misc. settings
+{{- if ne .Interface.ListenPort 0}}
+ListenPort = {{ .Interface.ListenPort }}
+{{- end}}
+{{- if ne .Interface.Mtu 0}}
+MTU = {{.Interface.Mtu}}
+{{- end}}
+{{- if ne .Interface.FirewallMark 0}}
+FwMark = {{.Interface.FirewallMark}}
+{{- end}}
+{{- if ne .Interface.RoutingTable ""}}
+Table = {{.Interface.RoutingTable}}
+{{- end}}
+{{- if .Interface.SaveConfig}}
+SaveConfig = true
+{{- end}}
+
+# Interface hooks
+PreUp = {{ .Interface.PreUp }}
+PostUp = {{ .Interface.PostUp }}
+PreDown = {{ .Interface.PreDown }}
+PostDown = {{ .Interface.PostDown }}
+
+#
+# Peers
+#
+
+{{range .Peers}}
+{{- if not .DeactivatedAt}}
+# -WGP- Peer: {{.Identifier}} / Updated: {{.UpdatedAt}} / Created: {{.CreatedAt}}
+# -WGP- Peer email: {{.Email}}
+# -WGP- PrivateKey: {{.PrivateKey}}
+[Peer]
+PublicKey = {{ .PublicKey }}
+{{- if .PresharedKey}}
+PresharedKey = {{ .PresharedKey }}
+{{- end}}
+AllowedIPs = {{ .AllowedIPsStr }}
+{{- if ne .Endpoint ""}}
+Endpoint = {{ .Endpoint }}
+{{- end}}
+{{- if ne .PersistentKeepalive 0}}
+PersistentKeepalive = {{ .PersistentKeepalive }}
+{{- end}}
+{{- end}}
+{{end}}
\ No newline at end of file
diff --git a/internal/wireguard/tpl/peer.tpl b/internal/wireguard/tpl/peer.tpl
new file mode 100644
index 0000000..ac55305
--- /dev/null
+++ b/internal/wireguard/tpl/peer.tpl
@@ -0,0 +1,28 @@
+# AUTOGENERATED FILE - PROVIDED BY WIREGUARD PORTAL
+# WireGuard configuration: {{ .Peer.Identifier }}
+# -WGP- PublicKey: {{ .Peer.PublicKey }}
+
+[Interface]
+
+# Core settings
+PrivateKey = {{ .Peer.PrivateKey }}
+Address = {{ .Peer.IPsStr }}
+
+# Misc. settings
+{{- if .Peer.DNSStr}}
+DNS = {{ .Peer.DNSStr }}
+{{- end}}
+{{- if ne .Peer.Mtu 0}}
+MTU = {{.Peer.Mtu}}
+{{- end}}
+
+[Peer]
+PublicKey = {{ .Peer.EndpointPublicKey }}
+Endpoint = {{ .Server.Endpoint }}
+AllowedIPs = {{ .Peer.AllowedIPsStr }}
+{{- if .Peer.PresharedKey}}
+PresharedKey = {{ .Peer.PresharedKey }}
+{{- end}}
+{{- if ne .Peer.PersistentKeepalive 0}}
+PersistentKeepalive = {{.Peer.PersistentKeepalive}}
+{{- end}}
\ No newline at end of file