diff --git a/server/lib/classes/cron.d/600-jailkit_maintenance.inc.php b/server/lib/classes/cron.d/600-jailkit_maintenance.inc.php
index 645004ff5253047e42c3e3a5748049a8da3e20da..06a278fa424381d82dd89027a270f3461ef98587 100644
--- a/server/lib/classes/cron.d/600-jailkit_maintenance.inc.php
+++ b/server/lib/classes/cron.d/600-jailkit_maintenance.inc.php
@@ -61,15 +61,15 @@ class cronjob_jailkit_maintenance extends cronjob {
 			print "Migration mode active, not running Jailkit updates.\n";
 		}
 
-		$update_options = array( 'allow_hardlink', );
-
 		$jailkit_config = $app->getconf->get_server_config($conf['server_id'], 'jailkit');
 		if (isset($this->jailkit_config) && isset($this->jailkit_config['jailkit_hardlinks'])) {
 			if ($this->jailkit_config['jailkit_hardlinks'] == 'yes') {
 				$update_options = array( 'hardlink', );
 			} elseif ($this->jailkit_config['jailkit_hardlinks'] == 'no') {
-				unset($update_options['allow_hardlink']);
+				$update_optiosn = array();
 			}
+		} else {
+			$update_options = array( 'allow_hardlink', );
 		}
 
 		// limit the number of jails we update at one time according to time of day
diff --git a/server/lib/classes/functions.inc.php b/server/lib/classes/functions.inc.php
index 1d9dd67569448a2cee200ec71281685fd568517a..5da1f3d713029b069b3a89c954dc001a541e1271 100644
--- a/server/lib/classes/functions.inc.php
+++ b/server/lib/classes/functions.inc.php
@@ -118,6 +118,26 @@ class functions {
 		return $out;
 	}
 
+	public function array_unset_by_value($array, $value) {
+		if (!is_array($array)) {
+			return $array;
+		}
+		if (is_array($value)) {
+			foreach ($array as $key => $val){
+				if (in_array($val, $value)) {
+					unset($array[$key]);
+				}
+			}
+		} else {
+			foreach ($array as $key => $val){
+				if ($val == $value) {
+					unset($array[$key]);
+				}
+			}
+		}
+		return $array;
+	}
+
 	public function currency_format($number, $view = '') {
 		global $app;
 		if($view != '') $number_format_decimals = (int)$app->lng('number_format_decimals_'.$view);
diff --git a/server/lib/classes/system.inc.php b/server/lib/classes/system.inc.php
index 25d8b8fb82fe632991a05f05cc4f08620eff4d39..c0dd80e3b4f9fa339a0960f656b8d9f4ec472247 100644
--- a/server/lib/classes/system.inc.php
+++ b/server/lib/classes/system.inc.php
@@ -949,15 +949,17 @@ class system{
 		}
 
 		$path = rtrim($path, '/');
-		if (is_dir($path)) {
+		if (is_dir($path) && !is_link($path)) {
 			$objects = array_diff(scandir($path), array('.', '..'));
 			foreach ($objects as $object) {
 				if ($recursive) {
-					if (is_dir("$path/$object")) {
+					if (is_dir("$path/$object") && !is_link("$path/$object")) {
 						$this->rmdir("$path/$object", $recursive); 
 					} else {
 						unlink ("$path/$object");
 					}
+				} else {
+					$app->log("rmdir: invoked non-recursive, not removing $path (expect rmdir failure)", LOGLEVEL_DEBUG);
 				}
 			}
 			return rmdir($path);
@@ -1011,16 +1013,18 @@ class system{
 		if ($path != '/') {
 			$path = rtrim($path, '/');
 		}
+global $app;
+#$app->log("remove_broken_symlinks: checking path: $path", LOGLEVEL_DEBUG);
 		if (is_dir($path)) {
+#$app->log("remove_broken_symlinks: $path is dir, running scandir", LOGLEVEL_DEBUG);
 			$objects = array_diff(scandir($path), array('.', '..'));
 			foreach ($objects as $object) {
-				if ($recursive) {
-					if (is_dir("$path/$object")) {
-						$this->remove_broken_symlinks("$path/$object", $recursive); 
-					} elseif (is_link("$path/$object") && !file_exists("$path/$object")) {
+#$app->log("remove_broken_symlinks: scandir found $object", LOGLEVEL_DEBUG);
+				if (is_dir("$path/$object") && $recursive) {
+					$this->remove_broken_symlinks("$path/$object", $recursive); 
+				} elseif (is_link("$path/$object") && !file_exists("$path/$object")) {
 $app->log("removing broken symlink $path/$object", LOGLEVEL_DEBUG);
-						unlink ("$path/$object");
-					}
+					unlink ("$path/$object");
 				}
 			}
 		} elseif (is_link("$path") && !file_exists("$path")) {
@@ -2299,10 +2303,12 @@ $app->log("removing broken symlink $path", LOGLEVEL_DEBUG);
 		$program_args = '';
 		foreach ($options as $opt) {
 			switch ($opt) {
-			case '-k|hardlink':
+			case '-k':
+			case 'hardlink':
 				$program_args .= ' -k';
 				break;
-			case '-f|force':
+			case '-f':
+			case 'force':
 				$program_args .= ' -f';
 				break;
 			}
@@ -2365,10 +2371,12 @@ $app->log("removing broken symlink $path", LOGLEVEL_DEBUG);
 		$program_args = '';
 		foreach ($options as $opt) {
 			switch ($opt) {
-			case '-k|hardlink':
+			case '-k':
+			case 'hardlink':
 				$program_args .= ' -k';
 				break;
-			case '-f|force':
+			case '-f':
+			case 'force':
 				$program_args .= ' -f';
 				break;
 			}
@@ -2400,6 +2408,7 @@ $app->log("removing broken symlink $path", LOGLEVEL_DEBUG);
 	public function update_jailkit_chroot($home_dir, $sections = array(), $programs = array(), $options = array()) {
 		global $app;
 
+$app->log("update_jailkit_chroot called for $home_dir with options ".print_r($options, true), LOGLEVEL_DEBUG);
 		$app->uses('ini_parser');
 
 		// Disallow operating on root directory
@@ -2416,10 +2425,12 @@ $app->log("removing broken symlink $path", LOGLEVEL_DEBUG);
 		$opts = array();
 		foreach ($options as $opt) {
 			switch ($opt) {
-			case '-k|hardlink':
+			case '-k':
+			case 'hardlink':
 				$opts[] = 'hardlink';
 				break;
-			case '-f|force':
+			case '-f':
+			case 'force':
 				$opts[] = 'force';
 				break;
 			}
@@ -2460,13 +2471,14 @@ $app->log("removing broken symlink $path", LOGLEVEL_DEBUG);
 				continue;
 			}
 
-			$this->remove_broken_symlinks($dir, true);
+			$this->remove_broken_symlinks($jail_dir, true);
 
 			// save list of hardlinked files
-			if (!in_array('hardlink', $opts) && !in_array('allow_hardlink', $options)) {
+			if (!(in_array('hardlink', $opts) || in_array('allow_hardlink', $options))) {
+$app->log("update_jailkit_chroot: searching for hardlinks in $jail_dir", LOGLEVEL_DEBUG);
                                 $find_multiple_links = function ( $path ) use ( &$find_multiple_links ) {
 					$found = array();
-					if (is_dir($path)) {
+					if (is_dir($path) && !is_link($path)) {
 						$objects = array_diff(scandir($path), array('.', '..'));
 						foreach ($objects as $object) {
 							$ret = $find_multiple_links( "$path/$object" );
@@ -2474,8 +2486,8 @@ $app->log("removing broken symlink $path", LOGLEVEL_DEBUG);
 								$found = array_merge($found, $ret);
 							}
 						}
-					} else {
-						$stat = stat($path);
+					} elseif (is_file($path)) {
+						$stat = lstat($path);
 						if ($stat['nlink'] > 1) {
 							$found[$path] = $path;
 						}
@@ -2483,34 +2495,42 @@ $app->log("removing broken symlink $path", LOGLEVEL_DEBUG);
 					return $found;
 				};
 
-				$ret = $find_multiple_links( $jail_dir );
+				$ret = $find_multiple_links($jail_dir);
 				if (count($ret) > 0) {
 					$multiple_links = array_merge($multiple_links, $ret);
 				}
-			}
 
-			// remove broken symlinks a second time after hardlink cleanup
-			$this->remove_broken_symlinks($dir, true);
+				// remove broken symlinks a second time after hardlink cleanup
+				$this->remove_broken_symlinks($jail_dir, true);
+			}
+else { $app->log("update_jailkit_chroot: NOT searching for hardlinks in $jail_dir, options: ".print_r($options, true), LOGLEVEL_DEBUG); }
 		}
 
+		foreach ($multiple_links as $file) {
+			$app->log("update_jailkit_chroot: removing hardlinked file: $file", LOGLEVEL_DEBUG);
+			unlink($file);
+		}
 		
-		$cmd = 'jk_update --jail='.escapeshellarg($home_dir) . $skips;
-		exec($cmd, $out, $ret);
-		foreach ($out as $line) {
+		$cmd = 'jk_update --jail=?' . $skips;
+		$this->exec_safe($cmd, $home_dir);
+$app->log('jk_update returned: '.print_r($this->_last_exec_out, true), LOGLEVEL_DEBUG);
+		foreach ($this->_last_exec_out as $line) {
 			if (substr( $line, 0, 4 ) === "skip") {
 				continue;
 			}
-			if (preg_match('@^(? [^ ]+){6}(.+)'.preg_quote($home_dir, '@').'$@', $line, $matches)) {
+			if (preg_match('@^(?: [^ ]+){6}(.+)'.preg_quote($home_dir, '@').'$@', $line, $matches)) {
 				# remove deprecated files that jk_update failed to remove
 				if (is_file($matches[1])) {
-$app->log("removing deprecated file which jk_update failed to remove:  ".$matches[1], LOGLEVEL_DEBUG);
+$app->log("update_jailkit_chroot: removing deprecated file which jk_update failed to remove:  ".$matches[1], LOGLEVEL_DEBUG);
 					unlink($matches[1]);
 				} elseif (is_dir($matches[1])) {
-$app->log("removing deprecated directory which jk_update failed to remove:  ".$matches[1], LOGLEVEL_DEBUG);
+$app->log("update_jailkit_chroot: removing deprecated directory which jk_update failed to remove:  ".$matches[1], LOGLEVEL_DEBUG);
 					$this->rmdir($matches[1], true);
 				}
                			# unhandled error
-				$app->log("jk_update error for jail $home_dir:  ".$matches[1], LOGLEVEL_DEBUG);
+				//$app->log("jk_update error for jail $home_dir:  ".$matches[1], LOGLEVEL_DEBUG);
+				// at least for 3.2 beta, lets gather some of this info:
+				$app->log("jk_update error for jail $home_dir, feel free to pass to ispconfig developers:  ".print_r( $matches, true), LOGLEVEL_DEBUG);
 			}
 		}
 
@@ -2528,20 +2548,20 @@ $app->log("removing deprecated directory which jk_update failed to remove:  ".$m
 		}
 
 		// search for any hardlinked files which are now missing
-		if (!in_array('hardlink', $opts) && !in_array('allow_hardlink', $options)) {
+		if (!(in_array('hardlink', $opts) || in_array('allow_hardlink', $options))) {
 			foreach ($multiple_links as $file) {
 				if (!is_file($file)) {
 					// strip $home_dir from $file
-					if (substr($file, 0, strlen(rtrim($home_dir, '/'))) == strlen(rtrim($home_dir, '/'))) {
+					if (substr($file, 0, strlen(rtrim($home_dir, '/'))) == rtrim($home_dir, '/')) {
 						$file = substr($file, strlen(rtrim($home_dir, '/')));
 					}
 					if (is_file($file)) { // file exists in root
-$app->log("file with multiple links still missing, running jk_cp to restore: $file", LOGLEVEL_DEBUG);
+						$app->log("update_jailkit_chroot: previously hardlinked file still missing, running jk_cp to restore: $file", LOGLEVEL_DEBUG);
 						$cmd = 'jk_cp -j ? ' . escapeshellarg($file);
 						$this->exec_safe($cmd, $home_dir);
 					} else {
 						// not necessarily an error
-						$app->log("previously hardlinked file was not found to restore: $file", LOGLEVEL_DEBUG);
+						$app->log("update_jailkit_chroot: previously hardlinked file was not restored and is no longer present in system: $file", LOGLEVEL_DEBUG);
 					}
 				}
 			}
@@ -2597,13 +2617,17 @@ $app->log("file with multiple links still missing, running jk_cp to restore: $fi
 			'sys',
 			'usr',
 			'var',
+			'run',		# not used by jailkit, but added for cleanup
 		);
 
 		$removed = '';
 		foreach ($jailkit_directories as $dir) {
 			$jail_dir = rtrim($home_dir, '/') . '/'.$dir;
 
-			if (is_dir($jail_dir)) {
+			if (is_link($jail_dir)) {
+				unlink($jail_dir);
+				$removed .= ' /'.$dir;
+			} elseif (is_dir($jail_dir)) {
 				$this->rmdir($jail_dir, true);
 				$removed .= ' /'.$dir;
 			}
@@ -2612,10 +2636,11 @@ $app->log("file with multiple links still missing, running jk_cp to restore: $fi
 
 		$app->log("delete_jailkit_chroot: removed from jail $home_dir: $removed", LOGLEVEL_DEBUG);
 
-		// handle etc and home special
+		// remove /home if empty
 		$home = rtrim($home_dir, '/') . '/home';
 		@rmdir($home);  # ok to fail if non-empty
 
+		// otherwise archive under /private
 		$private = rtrim($home_dir, '/') . '/private';
 		if (is_dir($home) && is_dir($private)) {
 			$archive = $private.'/home-'.date('c');
diff --git a/server/plugins-available/cron_jailkit_plugin.inc.php b/server/plugins-available/cron_jailkit_plugin.inc.php
index 2836ae0bdecbf3566fb71438265bd5437f3f2930..da17bdf38e281ee76a7ae0fac69f5b01f5f2f308 100644
--- a/server/plugins-available/cron_jailkit_plugin.inc.php
+++ b/server/plugins-available/cron_jailkit_plugin.inc.php
@@ -232,10 +232,20 @@ class cron_jailkit_plugin {
 	{
 		global $app;
 
+		if (isset($this->jailkit_config) && isset($this->jailkit_config['jailkit_hardlinks'])) {
+			if ($this->jailkit_config['jailkit_hardlinks'] == 'yes') {
+				$options = array( 'hardlink', );
+			} elseif ($this->jailkit_config['jailkit_hardlinks'] == 'no') {
+				$options = array();
+			}
+		} else {
+			$options = array( 'allow_hardlink', );
+		}
+
 		//check if the chroot environment is created yet if not create it with a list of program sections from the config
 		if (!is_dir($this->parent_domain['document_root'].'/etc/jailkit'))
 		{
-			$app->system->create_jailkit_chroot($this->parent_domain['document_root'], $this->jailkit_config['jailkit_chroot_app_sections']);
+			$app->system->create_jailkit_chroot($this->parent_domain['document_root'], $this->jailkit_config['jailkit_chroot_app_sections'], $options);
 
 			$this->app->log("Added jailkit chroot", LOGLEVEL_DEBUG);
 
@@ -267,34 +277,26 @@ class cron_jailkit_plugin {
 			$app->system->file_put_contents($motd, $tpl->grab());
 
 		} else {
-			$options = array( 'allow_hardlink', );
-			if (isset($this->jailkit_config) && isset($this->jailkit_config['jailkit_hardlinks'])) {
-				if ($this->jailkit_config['jailkit_hardlinks'] == 'yes') {
-					$options = array( 'hardlink', );
-				} elseif ($this->jailkit_config['jailkit_hardlinks'] == 'no') {
-					unset($options['allow_hardlink']);
-				}
-			}
 			// force update existing jails
 			$options[] = 'force';
 
 			$sections = $this->jailkit_config['jailkit_chroot_app_sections'];
 			$programs = $this->jailkit_config['jailkit_chroot_app_programs'];
 
-			$app->system->update_jailkit_chroot($this->data['new']['dir'], $sections, $programs, $options);
+			$app->system->update_jailkit_chroot($this->parent_domain['document_root'], $sections, $programs, $options);
 		}
-		$this->_add_jailkit_programs();
 
-		// might need to update master db here?  checking....
+		$this->_add_jailkit_programs($options);
+
+		// this gets last_jailkit_update out of sync with master db, but that is ok,
+		// as it is only used as a timestamp to moderate the frequency of updating on the slaves
 		$app->db->query("UPDATE `web_domain` SET `last_jailkit_update` = NOW() WHERE `document_root` = ?", $this->data['new']['dir']);
 	}
 
-	function _add_jailkit_programs()
+	function _add_jailkit_programs($opts=array())
 	{
 		global $app;
 
-		$opts = array('force');
-
 		//copy over further programs and its libraries
 		$app->system->create_jailkit_programs($this->parent_domain['document_root'], $this->jailkit_config['jailkit_chroot_app_programs'], $opts);
 		$this->app->log("Added app programs to jailkit chroot", LOGLEVEL_DEBUG);
diff --git a/server/plugins-available/shelluser_jailkit_plugin.inc.php b/server/plugins-available/shelluser_jailkit_plugin.inc.php
index 7d581c5f08f15f8db2efba999537445f9f6537fb..5a9f97f90423b7e2c08a39ce4f5494708820157d 100755
--- a/server/plugins-available/shelluser_jailkit_plugin.inc.php
+++ b/server/plugins-available/shelluser_jailkit_plugin.inc.php
@@ -290,13 +290,23 @@ class shelluser_jailkit_plugin {
 	{
 		global $app;
 
+		if (isset($this->jailkit_config) && isset($this->jailkit_config['jailkit_hardlinks'])) {
+			if ($this->jailkit_config['jailkit_hardlinks'] == 'yes') {
+				$options = array( 'hardlink', );
+			} elseif ($this->jailkit_config['jailkit_hardlinks'] == 'no') {
+				$options = array();
+			}
+		} else {
+			$options = array( 'allow_hardlink', );
+		}
+
 		//check if the chroot environment is created yet if not create it with a list of program sections from the config
 		if (!is_dir($this->data['new']['dir'].'/etc/jailkit'))
 		{
-			$app->system->create_jailkit_chroot($this->data['new']['dir'], $this->jailkit_config['jailkit_chroot_app_sections']);
+			$app->system->create_jailkit_chroot($this->data['new']['dir'], $this->jailkit_config['jailkit_chroot_app_sections'], $options);
 			$this->app->log("Added jailkit chroot", LOGLEVEL_DEBUG);
 
-			$this->_add_jailkit_programs();
+			$this->_add_jailkit_programs($options);
 
 			//add bash.bashrc script
 			//we need to collect the domain name to be used as the HOSTNAME in the bashrc script
@@ -330,14 +340,6 @@ class shelluser_jailkit_plugin {
 			$app->system->file_put_contents($motd, $tpl->grab());
 
 		} else {
-			$options = array( 'allow_hardlink', );
-			if (isset($this->jailkit_config) && isset($this->jailkit_config['jailkit_hardlinks'])) {
-				if ($this->jailkit_config['jailkit_hardlinks'] == 'yes') {
-					$options = array( 'hardlink', );
-				} elseif ($this->jailkit_config['jailkit_hardlinks'] == 'no') {
-					unset($options['allow_hardlink']);
-				}
-			}
 			// force update existing jails
 			$options[] = 'force';
 
@@ -347,11 +349,12 @@ class shelluser_jailkit_plugin {
 			$app->system->update_jailkit_chroot($this->data['new']['dir'], $sections, $programs, $options);
 		}
 
-		// might need to update master db here?  checking....
+		// this gets last_jailkit_update out of sync with master db, but that is ok,
+		// as it is only used as a timestamp to moderate the frequency of updating on the slaves
 		$app->db->query("UPDATE `web_domain` SET `last_jailkit_update` = NOW() WHERE `document_root` = ?", $this->data['new']['dir']);
 	}
 
-	function _add_jailkit_programs()
+	function _add_jailkit_programs($opts=array())
 	{
 		global $app;
 		$jailkit_chroot_app_programs = preg_split("/[\s,]+/", $this->jailkit_config['jailkit_chroot_app_programs']);
@@ -360,7 +363,7 @@ class shelluser_jailkit_plugin {
 				$jailkit_chroot_app_program = trim($jailkit_chroot_app_program);
 				if(is_file($jailkit_chroot_app_program) || is_dir($jailkit_chroot_app_program)){			
 					//copy over further programs and its libraries
-					$app->system->create_jailkit_programs($this->data['new']['dir'], $jailkit_chroot_app_program);
+					$app->system->create_jailkit_programs($this->data['new']['dir'], $jailkit_chroot_app_program, $opts);
 					$this->app->log("Added programs to jailkit chroot", LOGLEVEL_DEBUG);
 				}
 			}