diff --git a/data/Dockerfiles/netfilter/Dockerfile b/data/Dockerfiles/netfilter/Dockerfile
index 9dbb9b16..aab9b1d3 100644
--- a/data/Dockerfiles/netfilter/Dockerfile
+++ b/data/Dockerfiles/netfilter/Dockerfile
@@ -1,11 +1,11 @@
-FROM alpine:3.6
+FROM alpine:3.8
 LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
 
 ENV XTABLES_LIBDIR /usr/lib/xtables
 ENV PYTHON_IPTABLES_XTABLES_VERSION 12
 ENV IPTABLES_LIBDIR /usr/lib
 
-RUN apk add -U python2 python-dev py-pip gcc musl-dev iptables ip6tables \
+RUN apk add -U python2 python-dev py-pip gcc musl-dev iptables ip6tables tzdata \
   && pip2 install --upgrade python-iptables==0.13.0 redis ipaddress \
   && apk del python-dev py2-pip gcc
 
diff --git a/data/Dockerfiles/netfilter/server.py b/data/Dockerfiles/netfilter/server.py
index 5e21e27d..47c7539e 100644
--- a/data/Dockerfiles/netfilter/server.py
+++ b/data/Dockerfiles/netfilter/server.py
@@ -6,8 +6,9 @@ import time
 import atexit
 import signal
 import ipaddress
-import subprocess
+from random import randint
 from threading import Thread
+from threading import Lock
 import redis
 import time
 import json
@@ -27,6 +28,7 @@ RULES[6] = 'mailcow UI: Invalid password for .+ by ([0-9a-f\.:]+)'
 bans = {}
 log = {}
 quit_now = False
+lock = Lock()
 
 def refreshF2boptions():
   global f2boptions
@@ -55,38 +57,41 @@ def refreshF2boptions():
 if r.exists('F2B_LOG'):
   r.rename('F2B_LOG', 'NETFILTER_LOG')
 
-def checkChainOrder():
+def mailcowChainOrder():
+  global lock
   global quit_now
   while not quit_now:
-    time.sleep(20)
-    filter4_table = iptc.Table(iptc.Table.FILTER)
-    filter6_table = iptc.Table6(iptc.Table6.FILTER)
-    filter4_table.refresh()
-    filter6_table.refresh()
-    for f in [filter4_table, filter6_table]:
-      forward_chain = iptc.Chain(f, 'FORWARD')
-      input_chain = iptc.Chain(f, 'INPUT')
-      for chain in [forward_chain, input_chain]:
-        target_found = False
-        for position, item in enumerate(chain.rules):
-          if item.target.name == 'MAILCOW':
-            target_found = True
-            if position != 0:
-              log['time'] = int(round(time.time()))
-              log['priority'] = 'crit'
-              log['message'] = 'Error in ' + chain.name + ' chain order, restarting container'
-              r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False))
-              print log['message']
-              quit_now = True
-        if not target_found:
-          log['time'] = int(round(time.time()))
-          log['priority'] = 'crit'
-          log['message'] = 'Error in ' + chain.name + ' chain: target not found, restarting container'
-          r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False))
-          print log['message']
-          quit_now = True
+    time.sleep(10)
+    with lock:
+      filter4_table = iptc.Table(iptc.Table.FILTER)
+      filter6_table = iptc.Table6(iptc.Table6.FILTER)
+      filter4_table.refresh()
+      filter6_table.refresh()
+      for f in [filter4_table, filter6_table]:
+        forward_chain = iptc.Chain(f, 'FORWARD')
+        input_chain = iptc.Chain(f, 'INPUT')
+        for chain in [forward_chain, input_chain]:
+          target_found = False
+          for position, item in enumerate(chain.rules):
+            if item.target.name == 'MAILCOW':
+              target_found = True
+              if position != 0:
+                log['time'] = int(round(time.time()))
+                log['priority'] = 'crit'
+                log['message'] = 'Error in ' + chain.name + ' chain order, restarting container'
+                r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False))
+                print log['message']
+                quit_now = True
+          if not target_found:
+            log['time'] = int(round(time.time()))
+            log['priority'] = 'crit'
+            log['message'] = 'Error in ' + chain.name + ' chain: MAILCOW target not found, restarting container'
+            r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False))
+            print log['message']
+            quit_now = True
 
 def ban(address):
+  global lock
   refreshF2boptions()
   BAN_TIME = int(f2boptions['ban_time'])
   MAX_ATTEMPTS = int(f2boptions['max_attempts'])
@@ -135,21 +140,23 @@ def ban(address):
     r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False))
     print 'Banning %s for %d minutes' % (net, BAN_TIME / 60)
     if type(ip) is ipaddress.IPv4Address:
-      chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), 'MAILCOW')
-      rule = iptc.Rule()
-      rule.src = net
-      target = iptc.Target(rule, "REJECT")
-      rule.target = target
-      if rule not in chain.rules:
-        chain.insert_rule(rule)
+      with lock:
+        chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), 'MAILCOW')
+        rule = iptc.Rule()
+        rule.src = net
+        target = iptc.Target(rule, "REJECT")
+        rule.target = target
+        if rule not in chain.rules:
+          chain.insert_rule(rule)
     else:
-      chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), 'MAILCOW')
-      rule = iptc.Rule6()
-      rule.src = net
-      target = iptc.Target(rule, "REJECT")
-      rule.target = target
-      if rule not in chain.rules:
-        chain.insert_rule(rule)
+      with lock:
+        chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), 'MAILCOW')
+        rule = iptc.Rule6()
+        rule.src = net
+        target = iptc.Target(rule, "REJECT")
+        rule.target = target
+        if rule not in chain.rules:
+          chain.insert_rule(rule)
     r.hset('F2B_ACTIVE_BANS', '%s' % net, log['time'] + BAN_TIME)
   else:
     log['time'] = int(round(time.time()))
@@ -159,6 +166,7 @@ def ban(address):
     print '%d more attempts in the next %d seconds until %s is banned' % (MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net)
 
 def unban(net):
+  global lock
   log['time'] = int(round(time.time())) 
   log['priority'] = 'info'
   r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False))
@@ -172,21 +180,23 @@ def unban(net):
   r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False))
   print 'Unbanning %s' % net
   if type(ipaddress.ip_network(net.decode('ascii'))) is ipaddress.IPv4Network:
-    chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), 'MAILCOW')
-    rule = iptc.Rule()
-    rule.src = net
-    target = iptc.Target(rule, "REJECT")
-    rule.target = target
-    if rule in chain.rules:
-      chain.delete_rule(rule)
+    with lock:
+      chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), 'MAILCOW')
+      rule = iptc.Rule()
+      rule.src = net
+      target = iptc.Target(rule, "REJECT")
+      rule.target = target
+      if rule in chain.rules:
+        chain.delete_rule(rule)
   else:
-    chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), 'MAILCOW')
-    rule = iptc.Rule6()
-    rule.src = net
-    target = iptc.Target(rule, "REJECT")
-    rule.target = target
-    if rule in chain.rules:
-      chain.delete_rule(rule)
+    with lock:
+      chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), 'MAILCOW')
+      rule = iptc.Rule6()
+      rule.src = net
+      target = iptc.Target(rule, "REJECT")
+      rule.target = target
+      if rule in chain.rules:
+        chain.delete_rule(rule)
   r.hdel('F2B_ACTIVE_BANS', '%s' % net)
   r.hdel('F2B_QUEUE_UNBAN', '%s' % net)
   if net in bans:
@@ -197,6 +207,7 @@ def quit(signum, frame):
   quit_now = True
 
 def clear():
+  global lock
   log['time'] = int(round(time.time()))
   log['priority'] = 'info'
   log['message'] = 'Clearing all bans'
@@ -204,29 +215,30 @@ def clear():
   print 'Clearing all bans'
   for net in bans.copy():
     unban(net)
-  filter4_table = iptc.Table(iptc.Table.FILTER)
-  filter6_table = iptc.Table6(iptc.Table6.FILTER)
-  for filter_table in [filter4_table, filter6_table]:
-    filter_table.autocommit = False
-    forward_chain = iptc.Chain(filter_table, "FORWARD")
-    input_chain = iptc.Chain(filter_table, "INPUT")
-    mailcow_chain = iptc.Chain(filter_table, "MAILCOW")
-    if mailcow_chain in filter_table.chains:
-      for rule in mailcow_chain.rules:
-        mailcow_chain.delete_rule(rule)
-      for rule in forward_chain.rules:
-        if rule.target.name == 'MAILCOW':
-          forward_chain.delete_rule(rule)
-      for rule in input_chain.rules:
-        if rule.target.name == 'MAILCOW':
-          input_chain.delete_rule(rule)
-      filter_table.delete_chain("MAILCOW")
-    filter_table.commit()
-    filter_table.refresh()
-    filter_table.autocommit = True
-  r.delete('F2B_ACTIVE_BANS')
-  r.delete('F2B_PERM_BANS')
-  pubsub.unsubscribe()
+  with lock:
+    filter4_table = iptc.Table(iptc.Table.FILTER)
+    filter6_table = iptc.Table6(iptc.Table6.FILTER)
+    for filter_table in [filter4_table, filter6_table]:
+      filter_table.autocommit = False
+      forward_chain = iptc.Chain(filter_table, "FORWARD")
+      input_chain = iptc.Chain(filter_table, "INPUT")
+      mailcow_chain = iptc.Chain(filter_table, "MAILCOW")
+      if mailcow_chain in filter_table.chains:
+        for rule in mailcow_chain.rules:
+          mailcow_chain.delete_rule(rule)
+        for rule in forward_chain.rules:
+          if rule.target.name == 'MAILCOW':
+            forward_chain.delete_rule(rule)
+        for rule in input_chain.rules:
+          if rule.target.name == 'MAILCOW':
+            input_chain.delete_rule(rule)
+        filter_table.delete_chain("MAILCOW")
+      filter_table.commit()
+      filter_table.refresh()
+      filter_table.autocommit = True
+    r.delete('F2B_ACTIVE_BANS')
+    r.delete('F2B_PERM_BANS')
+    pubsub.unsubscribe()
 
 def watch():
   log['time'] = int(round(time.time()))
@@ -235,6 +247,7 @@ def watch():
   r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False))
   pubsub.subscribe('F2B_CHANNEL')
   print 'Subscribing to Redis channel F2B_CHANNEL'
+
   while not quit_now:
     for item in pubsub.listen():
       for rule_id, rule_regex in RULES.iteritems():
@@ -252,34 +265,81 @@ def watch():
             r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False))
             ban(addr)
 
-def snat(snat_target):
-  def get_snat_rule():
+def snat4(snat_target):
+  global lock
+  global quit_now
+
+  def get_snat4_rule():
     rule = iptc.Rule()
     rule.src = os.getenv('IPV4_NETWORK', '172.22.1') + '.0/24'
     rule.dst = '!' + rule.src
     target = rule.create_target("SNAT")
     target.to_source = snat_target
     return rule
+
   while not quit_now:
-    time.sleep(5)
-    table = iptc.Table('nat')
-    table.refresh()
-    table.autocommit = False
-    chain = iptc.Chain(table, 'POSTROUTING')
-    if get_snat_rule() not in chain.rules:
-      log['time'] = int(round(time.time()))
-      log['priority'] = 'info'
-      log['message'] = 'Added POSTROUTING rule for source network ' + get_snat_rule().src + ' to SNAT target ' + snat_target
-      r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False))
-      print log['message']
-      chain.insert_rule(get_snat_rule())
-      table.commit()
-    else:
-      for position, item in enumerate(chain.rules):
-        if item == get_snat_rule():
-          if position != 0:
-            chain.delete_rule(get_snat_rule())
-            table.commit()
+    time.sleep(10)
+    with lock:
+      try:
+        table = iptc.Table('nat')
+        table.refresh()
+        chain = iptc.Chain(table, 'POSTROUTING')
+        table.autocommit = False
+        if get_snat4_rule() not in chain.rules:
+          log['time'] = int(round(time.time()))
+          log['priority'] = 'info'
+          log['message'] = 'Added POSTROUTING rule for source network ' + get_snat4_rule().src + ' to SNAT target ' + snat_target
+          r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False))
+          print log['message']
+          chain.insert_rule(get_snat4_rule())
+          table.commit()
+        else:
+          for position, item in enumerate(chain.rules):
+            if item == get_snat4_rule():
+              if position != 0:
+                chain.delete_rule(get_snat4_rule())
+          table.commit()
+        table.autocommit = True
+      except:
+        print 'Error running SNAT4, retrying...' 
+
+def snat6(snat_target):
+  global lock
+  global quit_now
+
+  def get_snat6_rule():
+    rule = iptc.Rule6()
+    rule.src = os.getenv('IPV6_NETWORK', 'fd4d:6169:6c63:6f77::/64')
+    rule.dst = '!' + rule.src
+    target = rule.create_target("SNAT")
+    target.to_source = snat_target
+    return rule
+
+  while not quit_now:
+    time.sleep(10)
+    with lock:
+      try:
+        table = iptc.Table6('nat')
+        table.refresh()
+        chain = iptc.Chain(table, 'POSTROUTING')
+        table.autocommit = False
+        if get_snat6_rule() not in chain.rules:
+          log['time'] = int(round(time.time()))
+          log['priority'] = 'info'
+          log['message'] = 'Added POSTROUTING rule for source network ' + get_snat6_rule().src + ' to SNAT target ' + snat_target
+          r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False))
+          print log['message']
+          chain.insert_rule(get_snat6_rule())
+          table.commit()
+        else:
+          for position, item in enumerate(chain.rules):
+            if item == get_snat6_rule():
+              if position != 0:
+                chain.delete_rule(get_snat6_rule())
+          table.commit()
+        table.autocommit = True
+      except:
+        print 'Error running SNAT6, retrying...' 
 
 def autopurge():
   while not quit_now:
@@ -297,6 +357,7 @@ def autopurge():
           unban(net)
 
 def initChain():
+  # Is called before threads start, no locking
   print "Initializing mailcow netfilter chain"
   # IPv4
   if not iptc.Chain(iptc.Table(iptc.Table.FILTER), "MAILCOW") in iptc.Table(iptc.Table.FILTER).chains:
@@ -355,7 +416,6 @@ def initChain():
           chain.insert_rule(rule)
           r.hset('F2B_PERM_BANS', '%s' % bl_key, int(round(time.time())))
 
-
 if __name__ == '__main__':
 
   # In case a previous session was killed without cleanup
@@ -372,19 +432,30 @@ if __name__ == '__main__':
       snat_ip = os.getenv('SNAT_TO_SOURCE').decode('ascii')
       snat_ipo = ipaddress.ip_address(snat_ip)
       if type(snat_ipo) is ipaddress.IPv4Address:
-        snat_thread = Thread(target=snat,args=(snat_ip,))
-        snat_thread.daemon = True
-        snat_thread.start()
+        snat4_thread = Thread(target=snat4,args=(snat_ip,))
+        snat4_thread.daemon = True
+        snat4_thread.start()
     except ValueError:
       print os.getenv('SNAT_TO_SOURCE') + ' is not a valid IPv4 address'
 
+  if os.getenv('SNAT6_TO_SOURCE') and os.getenv('SNAT6_TO_SOURCE') is not 'n':
+    try:
+      snat_ip = os.getenv('SNAT6_TO_SOURCE').decode('ascii')
+      snat_ipo = ipaddress.ip_address(snat_ip)
+      if type(snat_ipo) is ipaddress.IPv6Address:
+        snat6_thread = Thread(target=snat6,args=(snat_ip,))
+        snat6_thread.daemon = True
+        snat6_thread.start()
+    except ValueError:
+      print os.getenv('SNAT6_TO_SOURCE') + ' is not a valid IPv6 address'
+
   autopurge_thread = Thread(target=autopurge)
   autopurge_thread.daemon = True
   autopurge_thread.start()
 
-  chainwatch_thread = Thread(target=checkChainOrder)
-  chainwatch_thread.daemon = True
-  chainwatch_thread.start()
+  mailcowchainwatch_thread = Thread(target=mailcowChainOrder)
+  mailcowchainwatch_thread.daemon = True
+  mailcowchainwatch_thread.start()
 
   signal.signal(signal.SIGTERM, quit)
   atexit.register(clear)
diff --git a/generate_config.sh b/generate_config.sh
index 860afa4a..e0d77653 100755
--- a/generate_config.sh
+++ b/generate_config.sh
@@ -23,9 +23,14 @@ if [[ -f mailcow.conf ]]; then
   esac
 fi
 
-if [ -z "$MAILCOW_HOSTNAME" ]; then
-  read -p "Hostname (FQDN - example.org is not a valid FQDN): " -ei "mx.example.org" MAILCOW_HOSTNAME
-fi
+while [ -z "${MAILCOW_HOSTNAME}" ]; do
+  read -p "Hostname (FQDN): " -ei "mx.example.org" MAILCOW_HOSTNAME
+  DOTS=${MAILCOW_HOSTNAME//[^.]};
+  if [ ${#DOTS} -lt 2 ]; then
+    echo "${MAILCOW_HOSTNAME} is not a FQDN"
+    MAILCOW_HOSTNAME=
+  fi
+done
 
 if [[ -a /etc/timezone ]]; then
   TZ=$(cat /etc/timezone)
@@ -122,9 +127,12 @@ IPV4_NETWORK=172.22.1
 # Internal IPv6 subnet in fc00::/7
 IPV6_NETWORK=fd4d:6169:6c63:6f77::/64
 
-# Use this IP for outgoing connections (SNAT)
+# Use this IPv4 for outgoing connections (SNAT)
 #SNAT_TO_SOURCE=
 
+# Use this IPv6 for outgoing connections (SNAT)
+#SNAT6_TO_SOURCE=
+
 # Disable IPv6
 # mailcow-network will still be created as IPv6 enabled, all containers will be created
 # without IPv6 support.
diff --git a/update.sh b/update.sh
index 88903ad0..3020ba0d 100755
--- a/update.sh
+++ b/update.sh
@@ -48,6 +48,7 @@ CONFIG_ARRAY=(
   "IPV6_NETWORK"
   "LOG_LINES"
   "SNAT_TO_SOURCE"
+  "SNAT6_TO_SOURCE"
   "SYSCTL_IPV6_DISABLED"
   "COMPOSE_PROJECT_NAME"
   "SQL_PORT"
@@ -125,9 +126,15 @@ for option in ${CONFIG_ARRAY[@]}; do
   elif [[ ${option} == "SNAT_TO_SOURCE" ]]; then
     if ! grep -q ${option} mailcow.conf; then
       echo "Adding new option \"${option}\" to mailcow.conf"
-      echo '# Use this IP for outgoing connections (SNAT)' >> mailcow.conf
+      echo '# Use this IPv4 for outgoing connections (SNAT)' >> mailcow.conf
       echo "#SNAT_TO_SOURCE=" >> mailcow.conf
     fi
+  elif [[ ${option} == "SNAT6_TO_SOURCE" ]]; then
+    if ! grep -q ${option} mailcow.conf; then
+      echo "Adding new option \"${option}\" to mailcow.conf"
+      echo '# Use this IPv6 for outgoing connections (SNAT)' >> mailcow.conf
+      echo "#SNAT6_TO_SOURCE=" >> mailcow.conf
+    fi
   elif ! grep -q ${option} mailcow.conf; then
     echo "Adding new option \"${option}\" to mailcow.conf"
     echo "${option}=n" >> mailcow.conf