diff --git a/Makefile b/Makefile
index a5ffcad..645109e 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
 MXQ_VERSION_MAJOR = 0
-MXQ_VERSION_MINOR = 13
+MXQ_VERSION_MINOR = 16
 MXQ_VERSION_PATCH = 0
 MXQ_VERSION_EXTRA = "beta"
 MXQ_VERSIONDATE = 2013-2015
@@ -45,6 +45,11 @@ endif
 
 ########################################################################
 
+### strip /mxq from SYSCONFDIR if set
+ifeq ($(notdir ${SYSCONFDIR}),mxq)
+    override SYSCONFDIR := $(patsubst %/,%,$(dir ${SYSCONFDIR}))
+endif
+
 ### strip /mxq from LIBEXECDIR if set
 ifeq ($(notdir ${LIBEXECDIR}),mxq)
     override LIBEXECDIR := $(patsubst %/,%,$(dir ${LIBEXECDIR}))
@@ -57,11 +62,13 @@ CGIDIR = ${LIBEXECDIR}/mxq/cgi
 MXQ_MYSQL_DEFAULT_FILE  = ${SYSCONFDIR}/mxq/mysql.cnf
 MXQ_MYSQL_DEFAULT_GROUP = mxqclient
 
-MXQ_INITIAL_PATH = /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
+MXQ_INITIAL_PATH   = /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
+MXQ_INITIAL_TMPDIR = /tmp
 
 CFLAGS_MXQ_MYSQL_DEFAULT_FILE  = -DMXQ_MYSQL_DEFAULT_FILE=\"$(MXQ_MYSQL_DEFAULT_FILE)\"
 CFLAGS_MXQ_MYSQL_DEFAULT_GROUP = -DMXQ_MYSQL_DEFAULT_GROUP=\"$(MXQ_MYSQL_DEFAULT_GROUP)\"
 CFLAGS_MXQ_INITIAL_PATH        = -DMXQ_INITIAL_PATH=\"$(MXQ_INITIAL_PATH)\"
+CFLAGS_MXQ_INITIAL_TMPDIR      = -DMXQ_INITIAL_TMPDIR=\"$(MXQ_INITIAL_TMPDIR)\"
 
 MYSQL_CONFIG = mysql_config
 
@@ -69,8 +76,8 @@ OS_RELEASE = $(shell ./os-release)
 
 # special defaults for mariux64
 ifeq (${OS_RELEASE}, mariux64)
-   MXQ_INITIAL_PATH := ${MXQ_INITIAL_PATH}:/usr/local/package/bin
-
+   MXQ_INITIAL_PATH   := ${MXQ_INITIAL_PATH}:/usr/local/package/bin
+   MXQ_INITIAL_TMPDIR := /scratch/local
 endif
 
 ########################################################################
@@ -373,6 +380,7 @@ mxqd.o: $(mxq_job.h)
 mxqd.o: $(mx_mysql.h)
 mxqd.o: CFLAGS += $(CFLAGS_MYSQL)
 mxqd.o: CFLAGS += $(CFLAGS_MXQ_INITIAL_PATH)
+mxqd.o: CFLAGS += $(CFLAGS_MXQ_INITIAL_TMPDIR)
 mxqd.o: CFLAGS += -Wno-unused-but-set-variable
 
 clean: CLEAN += mxqd.o
@@ -506,7 +514,7 @@ install:: web/pages/mxq/mxq
 	$(call quiet-install,0755,$^,${DESTDIR}${CGIDIR}/mxq)
 
 install:: web/lighttpd.conf
-	$(call quiet-install,0644,$^,${DESTDIR}${LIBEXECDIR}/mxq/lighttpd.conf)
+	$(call quiet-install,0644,$^,${DESTDIR}${SYSCONFDIR}/mxq/lighttpd.conf)
 
 ########################################################################
 
diff --git a/mx_util.c b/mx_util.c
index 199bcd5..efde13f 100644
--- a/mx_util.c
+++ b/mx_util.c
@@ -752,6 +752,194 @@ int mx_open_newfile(char *fname)
     return fh;
 }
 
+int mx_read_first_line_from_file(char *fname, char **line)
+{
+    _mx_cleanup_fclose_ FILE *fp;
+    char *buf = NULL;
+    size_t n = 0;
+    ssize_t res;
+
+    fp = fopen(fname, "r");
+    if (!fp)
+        return -errno;
+
+    res = getline(&buf, &n, fp);
+    if (res == -1)
+        return -errno;
+
+    *line = buf;
+
+    if (!res)
+        return res;
+
+    res--;
+
+    if (buf[res] == '\n')
+        buf[res] = 0;
+
+    return res;
+}
+
+int mx_strscan_ull(char **str, unsigned long long int *to)
+{
+    unsigned long long int l;
+    char *s;
+    char *p;
+    char o = 0;
+    int res;
+
+    s = *str;
+
+    p = strchr(s, ' ');
+    if (p) {
+        o = *p;
+        *p = 0;
+        p++;
+    } else {
+        p = s + strlen(s);
+    }
+
+    res = mx_strtoull(s, &l);
+    if (o)
+        *(p-1) = o;
+
+    if (res == 0) {
+        *to = l;
+        *str = p;
+    }
+
+    return res;
+}
+
+int mx_strscan_ll(char **str, long long int *to)
+{
+    long long int l;
+    char *s;
+    char *p;
+    char o = 0;
+    int res;
+
+    s = *str;
+
+    p = strchr(s, ' ');
+    if (p) {
+        o = *p;
+        *p = 0;
+        p++;
+    } else {
+        p = s + strlen(s);
+    }
+
+    res = mx_strtoll(s, &l);
+    if (o)
+        *(p-1) = o;
+
+    if (res == 0) {
+        *to = l;
+        *str = p;
+    }
+
+    return res;
+}
+
+int mx_strscan_proc_pid_stat(char *str, struct proc_pid_stat *pps)
+{
+    size_t res = 0;
+    char *p;
+    char *s;
+
+    pps->comm = NULL;
+
+    s = str;
+
+    res += mx_strscan_ll(&s, &(pps->pid));
+
+    p = strrchr(s, ')');
+    if (!p)
+        return -(errno=EINVAL);
+
+    *p = 0;
+    s++;
+
+    pps->comm = mx_strdup_forever(s);
+    s = p + 2;
+
+    pps->state = *s;
+    res += !(*(s+1) == ' ');
+    s += 2;
+
+    res += mx_strscan_ll(&s, &(pps->ppid));
+    res += mx_strscan_ll(&s, &(pps->pgrp));
+    res += mx_strscan_ll(&s, &(pps->session));
+    res += mx_strscan_ll(&s, &(pps->tty_nr));
+    res += mx_strscan_ll(&s, &(pps->tpgid));
+    res += mx_strscan_ull(&s, &(pps->flags));
+    res += mx_strscan_ull(&s, &(pps->minflt));
+    res += mx_strscan_ull(&s, &(pps->cminflt));
+    res += mx_strscan_ull(&s, &(pps->majflt));
+    res += mx_strscan_ull(&s, &(pps->cmajflt));
+    res += mx_strscan_ull(&s, &(pps->utime));
+    res += mx_strscan_ull(&s, &(pps->stime));
+    res += mx_strscan_ll(&s, &(pps->cutime));
+    res += mx_strscan_ll(&s, &(pps->cstime));
+    res += mx_strscan_ll(&s, &(pps->priority));
+    res += mx_strscan_ll(&s, &(pps->nice));
+    res += mx_strscan_ll(&s, &(pps->num_threads));
+    res += mx_strscan_ll(&s, &(pps->itrealvalue));
+    res += mx_strscan_ull(&s, &(pps->starttime));
+    res += mx_strscan_ull(&s, &(pps->vsize));
+    res += mx_strscan_ll(&s, &(pps->rss));
+    res += mx_strscan_ull(&s, &(pps->rsslim));
+    res += mx_strscan_ull(&s, &(pps->startcode));
+    res += mx_strscan_ull(&s, &(pps->endcode));
+    res += mx_strscan_ull(&s, &(pps->startstack));
+    res += mx_strscan_ull(&s, &(pps->kstkesp));
+    res += mx_strscan_ull(&s, &(pps->kstkeip));
+    res += mx_strscan_ull(&s, &(pps->signal));
+    res += mx_strscan_ull(&s, &(pps->blocked));
+    res += mx_strscan_ull(&s, &(pps->sigignore));
+    res += mx_strscan_ull(&s, &(pps->sigcatch));
+    res += mx_strscan_ull(&s, &(pps->wchan));
+    res += mx_strscan_ull(&s, &(pps->nswap));
+    res += mx_strscan_ull(&s, &(pps->cnswap));
+    res += mx_strscan_ll(&s, &(pps->exit_signal));
+    res += mx_strscan_ll(&s, &(pps->processor));
+    res += mx_strscan_ull(&s, &(pps->rt_priority));
+    res += mx_strscan_ull(&s, &(pps->policy));
+    res += mx_strscan_ull(&s, &(pps->delayacct_blkio_ticks));
+    res += mx_strscan_ull(&s, &(pps->guest_time));
+    res += mx_strscan_ll(&s, &(pps->cguest_time));
+
+    if (res != 0)
+        return -(errno=EINVAL);
+
+    return 0;
+}
+
+int mx_proc_pid_stat(struct proc_pid_stat *pps, pid_t pid)
+{
+    _mx_cleanup_free_ char *fname = NULL;
+    _mx_cleanup_free_ char *line = NULL;
+    int res;
+
+    mx_asprintf_forever(&fname, "/proc/%d/stat", pid);
+
+    res = mx_read_first_line_from_file(fname, &line);
+    if (res < 0)
+        return res;
+
+    res = mx_strscan_proc_pid_stat(line, pps);
+    if (res < 0)
+        return res;
+
+    return 0;
+}
+
+void mx_proc_pid_stat_free(struct proc_pid_stat *pps)
+{
+    mx_free_null(pps->comm);
+}
+
 int mx_sleep(unsigned int seconds)
 {
     if (seconds)
diff --git a/mx_util.h b/mx_util.h
index d805aef..f79d77f 100644
--- a/mx_util.h
+++ b/mx_util.h
@@ -5,9 +5,57 @@
 #include <stdint.h>
 #include <stdarg.h>
 #include <string.h>
+#include <stdio.h>
 
 #include "mx_log.h"
 
+struct proc_pid_stat {
+    long long int pid;     /* 1 */
+    char *comm;            /* 2 (comm) */
+    char state;            /* 3 "RSDZTW" */
+    long long int ppid;    /* 4 */
+    long long int pgrp;    /* 5 */
+    long long int session; /* 6 */
+    long long int tty_nr;  /* 7 */
+    long long int tpgid;   /* 8 */
+    unsigned long long int flags;   /*  9 */
+    unsigned long long int minflt;  /* 10 */
+    unsigned long long int cminflt; /* 11 */
+    unsigned long long int majflt;  /* 12 */
+    unsigned long long int cmajflt; /* 13 */
+    unsigned long long int utime;   /* 14 */
+    unsigned long long int stime;   /* 15 */
+    long long int cutime;   /* 16 */
+    long long int cstime;   /* 17 */
+    long long int priority; /* 18 */
+    long long int nice;     /* 19 */
+    long long int num_threads;          /* 20 */
+    long long int itrealvalue;          /* 21 */
+    unsigned long long int starttime;   /* 22 */
+    unsigned long long int vsize;       /* 23 */
+    long long int rss;                  /* 24 */
+    unsigned long long int rsslim;      /* 25 */
+    unsigned long long int startcode;   /* 26 */
+    unsigned long long int endcode;     /* 27 */
+    unsigned long long int startstack;  /* 28 */
+    unsigned long long int kstkesp;     /* 29 */
+    unsigned long long int kstkeip;     /* 30 */
+    unsigned long long int signal;      /* 31 */
+    unsigned long long int blocked;     /* 32 */
+    unsigned long long int sigignore;   /* 33 */
+    unsigned long long int sigcatch;    /* 34 */
+    unsigned long long int wchan;       /* 35 */
+    unsigned long long int nswap;       /* 36 */
+    unsigned long long int cnswap;      /* 37 */
+    long long int exit_signal;          /* 38 */
+    long long int processor;            /* 39 */
+    unsigned long long int rt_priority; /* 40 */
+    unsigned long long int policy;      /* 41 */
+    unsigned long long int delayacct_blkio_ticks; /* 42 */
+    unsigned long long int guest_time;  /* 43 */
+    long long int cguest_time;          /* 44 */
+};
+
 #ifdef MX_NDEBUG
 #   include <assert.h>
 #   define mx_assert_return_minus_errno(test, eno) \
@@ -46,9 +94,17 @@ static inline void __mx_free(void *ptr) {
     free(*(void **)ptr);
 }
 
+static inline void __mx_fclose(FILE **ptr) {
+    if (*ptr)
+        fclose(*ptr);
+}
+
 #undef _mx_cleanup_free_
 #define _mx_cleanup_free_ _mx_cleanup_(__mx_free)
 
+#undef _mx_cleanup_fclose_
+#define _mx_cleanup_fclose_ _mx_cleanup_(__mx_fclose)
+
 #undef likely
 #define likely(x)       __builtin_expect((x),1)
 
@@ -106,6 +162,15 @@ int mx_setenvf_forever(const char *name, char *fmt, ...) __attribute__ ((format(
 
 int mx_open_newfile(char *fname);
 
+int mx_read_first_line_from_file(char *fname, char **line);
+
+int mx_strscan_ull(char **str, unsigned long long int *to);
+int mx_strscan_ll(char **str, long long int *to);
+int mx_strscan_proc_pid_stat(char *str, struct proc_pid_stat *pps);
+
+int mx_proc_pid_stat(struct proc_pid_stat *pps, pid_t pid);
+void mx_proc_pid_stat_free(struct proc_pid_stat *pps);
+
 int mx_sleep(unsigned int seconds);
 int mx_sleep_nofail(unsigned int seconds);
 
diff --git a/mxq_group.c b/mxq_group.c
index f9ec96a..65f7757 100644
--- a/mxq_group.c
+++ b/mxq_group.c
@@ -12,7 +12,7 @@
 #include "mx_util.h"
 #include "mx_mysql.h"
 
-#define GROUP_FIELDS_CNT 29
+#define GROUP_FIELDS_CNT 30
 #define GROUP_FIELDS \
             " group_id," \
             " group_name," \
@@ -27,6 +27,7 @@
             " job_threads," \
             " job_memory," \
             " job_time," \
+            " job_max_per_node," \
             " group_jobs," \
             " group_jobs_inq," \
             " group_jobs_running," \
@@ -70,6 +71,8 @@ static int bind_result_group_fields(struct mx_mysql_bind *result, struct mxq_gro
     res += mx_mysql_bind_var(result, idx++, uint64, &(g->job_memory));
     res += mx_mysql_bind_var(result, idx++, uint32, &(g->job_time));
 
+    res += mx_mysql_bind_var(result, idx++, uint16, &(g->job_max_per_node));
+
     res += mx_mysql_bind_var(result, idx++, uint64, &(g->group_jobs));
     res += mx_mysql_bind_var(result, idx++, uint64, &(g->group_jobs_inq));
     res += mx_mysql_bind_var(result, idx++, uint64, &(g->group_jobs_running));
@@ -97,16 +100,9 @@ static int bind_result_group_fields(struct mx_mysql_bind *result, struct mxq_gro
 void mxq_group_free_content(struct mxq_group *g)
 {
         mx_free_null(g->group_name);
-        g->_group_name_length = 0;
-
         mx_free_null(g->user_name);
-        g->_user_name_length = 0;
-
         mx_free_null(g->user_group);
-        g->_user_group_length = 0;
-
         mx_free_null(g->job_command);
-        g->_job_command_length = 0;
 }
 
 
diff --git a/mxq_group.h b/mxq_group.h
index 8ab438d..e678cb3 100644
--- a/mxq_group.h
+++ b/mxq_group.h
@@ -10,7 +10,6 @@ struct mxq_group {
    uint64_t  group_id;
 
    char *    group_name;
-   unsigned long  _group_name_length;
 
    uint8_t   group_status;
    uint64_t  group_flags;
@@ -18,19 +17,18 @@ struct mxq_group {
 
    uint32_t  user_uid;
    char *    user_name;
-   unsigned long  _user_name_length;
 
    uint32_t  user_gid;
    char *    user_group;
-   unsigned long  _user_group_length;
 
    char *    job_command;
-   unsigned long  _job_command_length;
 
    uint16_t  job_threads;
    uint64_t  job_memory;
    uint32_t  job_time;
 
+   uint16_t  job_max_per_node;
+
    uint64_t  group_jobs;
    uint64_t  group_jobs_inq;
    uint64_t  group_jobs_running;
diff --git a/mxq_job.c b/mxq_job.c
index 16a64af..68fc709 100644
--- a/mxq_job.c
+++ b/mxq_job.c
@@ -16,7 +16,7 @@
 #include "mxq_group.h"
 #include "mxq_job.h"
 
-#define JOB_FIELDS_CNT 34
+#define JOB_FIELDS_CNT 35
 #define JOB_FIELDS \
                 " job_id, " \
                 " job_status, " \
@@ -32,28 +32,29 @@
                 \
                 " job_umask, " \
                 " host_submit, " \
+                " host_id, " \
                 " server_id, " \
                 " host_hostname, " \
-                " host_pid, " \
                 \
+                " host_pid, " \
                 " host_slots, " \
                 " UNIX_TIMESTAMP(date_submit) as date_submit, " \
                 " UNIX_TIMESTAMP(date_start) as date_start, " \
                 " UNIX_TIMESTAMP(date_end) as date_end, " \
-                " stats_status, " \
                 \
+                " stats_status, " \
                 " stats_utime_sec, " \
                 " stats_utime_usec, " \
                 " stats_stime_sec, " \
                 " stats_stime_usec, " \
-                " stats_real_sec, " \
                 \
+                " stats_real_sec, " \
                 " stats_real_usec, " \
                 " stats_maxrss, " \
                 " stats_minflt, " \
                 " stats_majflt, " \
-                " stats_nswap, " \
                 \
+                " stats_nswap, " \
                 " stats_inblock, " \
                 " stats_oublock, " \
                 " stats_nvcsw, " \
@@ -81,28 +82,29 @@ static int bind_result_job_fields(struct mx_mysql_bind *result, struct mxq_job *
 
     res += mx_mysql_bind_var(result, idx++, uint32, &(j->job_umask));
     res += mx_mysql_bind_var(result, idx++, string, &(j->host_submit));
+    res += mx_mysql_bind_var(result, idx++, string, &(j->host_id));
     res += mx_mysql_bind_var(result, idx++, string, &(j->server_id));
     res += mx_mysql_bind_var(result, idx++, string, &(j->host_hostname));
-    res += mx_mysql_bind_var(result, idx++, uint32, &(j->host_pid));
 
+    res += mx_mysql_bind_var(result, idx++, uint32, &(j->host_pid));
     res += mx_mysql_bind_var(result, idx++, uint32, &(j->host_slots));
     res += mx_mysql_bind_var(result, idx++,  int64, &(j->date_submit));
     res += mx_mysql_bind_var(result, idx++,  int64, &(j->date_start));
     res += mx_mysql_bind_var(result, idx++,  int64, &(j->date_end));
-    res += mx_mysql_bind_var(result, idx++,  int32, &(j->stats_status));
 
+    res += mx_mysql_bind_var(result, idx++,  int32, &(j->stats_status));
     res += mx_mysql_bind_var(result, idx++,  int64, &(j->stats_rusage.ru_utime.tv_sec));
     res += mx_mysql_bind_var(result, idx++,  int64, &(j->stats_rusage.ru_utime.tv_usec));
     res += mx_mysql_bind_var(result, idx++,  int64, &(j->stats_rusage.ru_stime.tv_sec));
     res += mx_mysql_bind_var(result, idx++,  int64, &(j->stats_rusage.ru_stime.tv_usec));
-    res += mx_mysql_bind_var(result, idx++,  int64, &(j->stats_realtime.tv_sec));
 
+    res += mx_mysql_bind_var(result, idx++,  int64, &(j->stats_realtime.tv_sec));
     res += mx_mysql_bind_var(result, idx++,  int64, &(j->stats_realtime.tv_usec));
     res += mx_mysql_bind_var(result, idx++,  int64, &(j->stats_rusage.ru_maxrss));
     res += mx_mysql_bind_var(result, idx++,  int64, &(j->stats_rusage.ru_minflt));
     res += mx_mysql_bind_var(result, idx++,  int64, &(j->stats_rusage.ru_majflt));
-    res += mx_mysql_bind_var(result, idx++,  int64, &(j->stats_rusage.ru_nswap));
 
+    res += mx_mysql_bind_var(result, idx++,  int64, &(j->stats_rusage.ru_nswap));
     res += mx_mysql_bind_var(result, idx++,  int64, &(j->stats_rusage.ru_inblock));
     res += mx_mysql_bind_var(result, idx++,  int64, &(j->stats_rusage.ru_oublock));
     res += mx_mysql_bind_var(result, idx++,  int64, &(j->stats_rusage.ru_nvcsw));
@@ -154,16 +156,9 @@ char *mxq_job_status_to_name(uint64_t status)
 void mxq_job_free_content(struct mxq_job *j)
 {
         mx_free_null(j->job_workdir);
-        j->_job_workdir_length = 0;
-
         mx_free_null(j->job_argv_str);
-        j->_job_argv_str_length = 0;
-
         mx_free_null(j->job_stdout);
-        j->_job_stdout_length = 0;
-
         mx_free_null(j->job_stderr);
-        j->_job_stderr_length = 0;
 
         if (j->tmp_stderr == j->tmp_stdout) {
             j->tmp_stdout = NULL;
@@ -171,16 +166,10 @@ void mxq_job_free_content(struct mxq_job *j)
             mx_free_null(j->tmp_stdout);
         }
         mx_free_null(j->tmp_stderr);
-
         mx_free_null(j->host_submit);
-        j->_host_submit_length = 0;
-
+        mx_free_null(j->host_id);
         mx_free_null(j->server_id);
-        j->_server_id_length = 0;
-
         mx_free_null(j->host_hostname);
-        j->_host_hostname_length = 0;
-
         mx_free_null(j->job_argv);
         j->job_argv = NULL;
 }
@@ -391,19 +380,21 @@ int mxq_set_job_status_loaded_on_server(struct mx_mysql *mysql, struct mxq_job *
     char *query =
             "UPDATE mxq_job SET"
             " job_status = " status_str(MXQ_JOB_STATUS_LOADED)
+            ", host_id = ?"
             " WHERE job_id = ?"
             " AND job_status = " status_str(MXQ_JOB_STATUS_ASSIGNED)
             " AND host_hostname = ?"
             " AND server_id = ?"
             " AND host_pid = 0";
 
-    res = mx_mysql_bind_init_param(&param, 3);
+    res = mx_mysql_bind_init_param(&param, 4);
     assert(res == 0);
 
     res = 0;
-    res += mx_mysql_bind_var(&param, 0, uint64, &(job->job_id));
-    res += mx_mysql_bind_var(&param, 1, string, &(job->host_hostname));
-    res += mx_mysql_bind_var(&param, 2, string, &(job->server_id));
+    res += mx_mysql_bind_var(&param, 0, string, &(job->host_id));
+    res += mx_mysql_bind_var(&param, 1, uint64, &(job->job_id));
+    res += mx_mysql_bind_var(&param, 2, string, &(job->host_hostname));
+    res += mx_mysql_bind_var(&param, 3, string, &(job->server_id));
     assert(res == 0);
 
     res = mx_mysql_do_statement_noresult_retry_on_fail(mysql, query, &param);
@@ -662,7 +653,7 @@ int mxq_load_job_assigned_to_server(struct mx_mysql *mysql, struct mxq_job **mxq
     return res;
 }
 
-int mxq_load_job_from_group_for_server(struct mx_mysql *mysql, struct mxq_job *mxqjob, uint64_t group_id, char *hostname, char *server_id)
+int mxq_load_job_from_group_for_server(struct mx_mysql *mysql, struct mxq_job *mxqjob, uint64_t group_id, char *hostname, char *server_id, char *host_id)
 {
     int res;
     struct mxq_job *jobs   = NULL;
@@ -673,6 +664,8 @@ int mxq_load_job_from_group_for_server(struct mx_mysql *mysql, struct mxq_job *m
     assert(*hostname);
     assert(server_id);
     assert(*server_id);
+    assert(host_id);
+    assert(*host_id);
 
     do {
         res = mxq_load_job_assigned_to_server(mysql, &jobs, hostname, server_id);
@@ -697,6 +690,9 @@ int mxq_load_job_from_group_for_server(struct mx_mysql *mysql, struct mxq_job *m
         }
     } while (1);
 
+    mx_free_null(mxqjob->host_id);
+    mxqjob->host_id = mx_strdup_forever(host_id);
+
     res = mxq_set_job_status_loaded_on_server(mysql, mxqjob);
     if (res < 0) {
         mx_log_err("  group_id=%lu job_id=%lu :: mxq_set_job_status_loaded_on_server(): %m", group_id, mxqjob->job_id);
diff --git a/mxq_job.h b/mxq_job.h
index 5d78ba1..9e7ba35 100644
--- a/mxq_job.h
+++ b/mxq_job.h
@@ -19,18 +19,14 @@ struct mxq_job {
     struct mxq_group * group_ptr;
 
     char *     job_workdir;
-    unsigned long _job_workdir_length;
 
     uint16_t   job_argc;
 
     char **    job_argv;
     char *     job_argv_str;
-    unsigned long _job_argv_str_length;
 
     char *     job_stdout;
-    unsigned long _job_stdout_length;
     char *     job_stderr;
-    unsigned long _job_stderr_length;
 
     char *     tmp_stdout;
     char *     tmp_stderr;
@@ -38,13 +34,11 @@ struct mxq_job {
     uint32_t   job_umask;
 
     char *     host_submit;
-    unsigned long _host_submit_length;
 
+    char *     host_id;
     char *     server_id;
-    unsigned long _server_id_length;
 
     char *     host_hostname;
-    unsigned long _host_hostname_length;
 
     uint32_t   host_pid;
     uint32_t   host_slots;
@@ -107,6 +101,6 @@ int mxq_set_job_status_exited(struct mx_mysql *mysql, struct mxq_job *job);
 int mxq_set_job_status_unknown_for_server(struct mx_mysql *mysql, char *hostname, char *server_id);
 int mxq_job_set_tmpfilenames(struct mxq_group *g, struct mxq_job *j);
 int mxq_load_job_assigned_to_server(struct mx_mysql *mysql, struct mxq_job **mxq_jobs, char *hostname, char *server_id);
-int mxq_load_job_from_group_for_server(struct mx_mysql *mysql, struct mxq_job *mxqjob, uint64_t group_id, char *hostname, char *server_id);
+int mxq_load_job_from_group_for_server(struct mx_mysql *mysql, struct mxq_job *mxqjob, uint64_t group_id, char *hostname, char *server_id, char *host_id);
 
 #endif
diff --git a/mxqd.c b/mxqd.c
index 633f711..9ab4fda 100644
--- a/mxqd.c
+++ b/mxqd.c
@@ -34,43 +34,31 @@
 #include "mxq_job.h"
 #include "mx_mysql.h"
 #include "mxqd.h"
+#include "mxq.h"
 
-#ifndef MXQ_VERSION
-#define MXQ_VERSION "0.00"
-#endif
+#define MYSQL_DEFAULT_FILE     MXQ_MYSQL_DEFAULT_FILE
+#define MYSQL_DEFAULT_GROUP    "mxqd"
 
-#ifndef MXQ_VERSIONFULL
-#define MXQ_VERSIONFULL "MXQ v0.00 super alpha 0"
+#ifndef MXQ_INITIAL_PATH
+#  define MXQ_INITIAL_PATH      "/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin"
 #endif
 
-#ifndef MXQ_VERSIONDATE
-#define MXQ_VERSIONDATE "2015"
+#ifndef MXQ_INITIAL_TMPDIR
+#  define MXQ_INITIAL_TMPDIR    "/tmp"
 #endif
 
-#define MYSQL_DEFAULT_FILE     MXQ_MYSQL_DEFAULT_FILE
-#define MYSQL_DEFAULT_GROUP    "mxqd"
-
 volatile sig_atomic_t global_sigint_cnt=0;
 volatile sig_atomic_t global_sigterm_cnt=0;
 
 int mxq_redirect_output(char *stdout_fname, char *stderr_fname);
 
-static void print_version(void)
-{
-    printf(
-    "mxqd - " MXQ_VERSIONFULL "\n"
-    "  by Marius Tolzmann <tolzmann@molgen.mpg.de> " MXQ_VERSIONDATE "\n"
-    "  Max Planck Institute for Molecular Genetics - Berlin Dahlem\n"
-    );
-}
-
 static void print_usage(void)
 {
-    print_version();
+    mxq_print_generic_version();
     printf(
     "\n"
     "Usage:\n"
-    "  mxqd [options]\n"
+    "  %s [options]\n"
     "\n"
     "options:\n"
     "  -j, --slots     <slots>           default: 1\n"
@@ -85,18 +73,26 @@ static void print_usage(void)
     "      --no-log                      default: write a logfile\n"
     "      --debug                       default: info log level\n"
     "\n"
+    "      --initial-path <path>         default: %s\n"
+    "      --initial-tmpdir <directory>  default: %s\n"
+    "\n"
     "  -V, --version\n"
     "  -h, --help\n"
     "\n"
     "Change how to connect to the mysql server:\n"
     "\n"
-    "  -M, --mysql-default-file [mysql-file]    default: " MYSQL_DEFAULT_FILE "\n"
-    "  -S, --mysql-default-group [mysql-group]  default: " MYSQL_DEFAULT_GROUP "\n"
+    "  -M, --mysql-default-file [mysql-file]    default: %s\n"
+    "  -S, --mysql-default-group [mysql-group]  default: %s\n"
     "\n"
     "Environment:\n"
     "  MXQ_MYSQL_DEFAULT_FILE   change default for [mysql-file]\n"
     "  MXQ_MYSQL_DEFAULT_GROUP  change default for [mysql-group]\n"
-    "\n"
+    "\n",
+    program_invocation_short_name,
+    MXQ_INITIAL_PATH,
+    MXQ_INITIAL_TMPDIR,
+    MXQ_MYSQL_DEFAULT_FILE_STR,
+    MXQ_MYSQL_DEFAULT_GROUP_STR
     );
 }
 
@@ -203,13 +199,17 @@ int server_init(struct mxq_server *server, int argc, char *argv[])
     char *arg_mysql_default_group;
     char *arg_mysql_default_file;
     char *arg_pidfile = NULL;
+    char *arg_initial_path;
+    char *arg_initial_tmpdir;
     char arg_daemonize = 0;
     char arg_nolog = 0;
+    char *str_bootid;
     int opt;
     unsigned long threads_total = 1;
     unsigned long memory_total = 2048;
     unsigned long memory_max   = 0;
     int i;
+    struct proc_pid_stat pps = {0};
 
     struct mx_getopt_ctl optctl;
     struct mx_option opts[] = {
@@ -219,6 +219,8 @@ int server_init(struct mxq_server *server, int argc, char *argv[])
                 MX_OPTION_NO_ARG("no-log",               3),
                 MX_OPTION_NO_ARG("debug",                5),
                 MX_OPTION_REQUIRED_ARG("pid-file",       2),
+                MX_OPTION_REQUIRED_ARG("initial-path",   7),
+                MX_OPTION_REQUIRED_ARG("initial-tmpdir", 8),
                 MX_OPTION_REQUIRED_ARG("slots",        'j'),
                 MX_OPTION_REQUIRED_ARG("memory",       'm'),
                 MX_OPTION_REQUIRED_ARG("max-memory-per-slot", 'x'),
@@ -232,6 +234,9 @@ int server_init(struct mxq_server *server, int argc, char *argv[])
     arg_server_id = "main";
     arg_hostname  = mx_hostname();
 
+    arg_initial_path = MXQ_INITIAL_PATH;
+    arg_initial_tmpdir = MXQ_INITIAL_TMPDIR;
+
     arg_mysql_default_group = getenv("MXQ_MYSQL_DEFAULT_GROUP");
     if (!arg_mysql_default_group)
         arg_mysql_default_group = MYSQL_DEFAULT_GROUP;
@@ -272,7 +277,7 @@ int server_init(struct mxq_server *server, int argc, char *argv[])
                 break;
 
             case 'V':
-                print_version();
+                mxq_print_generic_version();
                 exit(EX_USAGE);
 
             case 'h':
@@ -318,6 +323,14 @@ int server_init(struct mxq_server *server, int argc, char *argv[])
                 arg_server_id = optctl.optarg;
                 break;
 
+            case 7:
+                arg_initial_path = optctl.optarg;
+                break;
+
+            case 8:
+                arg_initial_tmpdir = optctl.optarg;
+                break;
+
             case 'M':
                 arg_mysql_default_file = optctl.optarg;
                 break;
@@ -351,6 +364,8 @@ int server_init(struct mxq_server *server, int argc, char *argv[])
 
     server->hostname = arg_hostname;
     server->server_id = arg_server_id;
+    server->initial_path = arg_initial_path;
+    server->initial_tmpdir = arg_initial_tmpdir;
 
     server->flock = mx_flock(LOCK_EX, "/dev/shm/mxqd.%s.%s.lck", server->hostname, server->server_id);
     if (!server->flock) {
@@ -396,6 +411,20 @@ int server_init(struct mxq_server *server, int argc, char *argv[])
         }
     }
 
+    res = mx_read_first_line_from_file("/proc/sys/kernel/random/boot_id", &str_bootid);
+    assert(res == 36);
+    assert(str_bootid);
+
+    server->boot_id = str_bootid;
+
+    res = mx_proc_pid_stat(&pps, getpid());
+    assert(res == 0);
+
+    server->starttime = pps.starttime;
+    mx_proc_pid_stat_free(&pps);
+
+    mx_asprintf_forever(&server->host_id, "%s-%llx-%x", server->boot_id, server->starttime, getpid());
+
     server->slots = threads_total;;
     server->memory_total = memory_total;
     server->memory_max_per_slot = memory_max;
@@ -455,6 +484,10 @@ void group_init(struct mxq_group_list *group)
     }
     jobs_max /= g->job_threads;
 
+    /* limit maximum number of jobs on user/group request */
+    if (g->job_max_per_node && jobs_max > g->job_max_per_node)
+        jobs_max = g->job_max_per_node;
+
     slots_max = jobs_max * slots_per_job;
     memory_max = jobs_max * g->job_memory;
 
@@ -778,7 +811,8 @@ static int init_child_process(struct mxq_group_list *group, struct mxq_job *j)
     mx_setenv_forever("USER",     g->user_name);
     mx_setenv_forever("USERNAME", g->user_name);
     mx_setenv_forever("LOGNAME",  g->user_name);
-    mx_setenv_forever("PATH",     MXQ_INITIAL_PATH);
+    mx_setenv_forever("PATH",     s->initial_path);
+    mx_setenv_forever("TMPDIR",   s->initial_tmpdir);
     mx_setenv_forever("PWD",      j->job_workdir);
     mx_setenv_forever("HOME",     passwd->pw_dir);
     mx_setenv_forever("SHELL",    passwd->pw_shell);
@@ -789,7 +823,9 @@ static int init_child_process(struct mxq_group_list *group, struct mxq_job *j)
     mx_setenvf_forever("MXQ_SLOTS",   "%lu",    group->slots_per_job);
     mx_setenvf_forever("MXQ_MEMORY",  "%lu",    g->job_memory);
     mx_setenvf_forever("MXQ_TIME",    "%d",     g->job_time);
-    mx_setenvf_forever("MXQ_HOSTID",  "%s::%s", s->hostname, s->server_id);
+    mx_setenv_forever("MXQ_HOSTID",   s->host_id);
+    mx_setenv_forever("MXQ_HOSTNAME", s->hostname);
+    mx_setenv_forever("MXQ_SERVERID", s->server_id);
 
     fh = open("/proc/self/loginuid", O_WRONLY|O_TRUNC);
     if (fh == -1) {
@@ -983,7 +1019,7 @@ unsigned long start_job(struct mxq_group_list *group)
 
     server = group->user->server;
 
-    res = mxq_load_job_from_group_for_server(server->mysql, &mxqjob, group->group.group_id, server->hostname, server->server_id);
+    res = mxq_load_job_from_group_for_server(server->mysql, &mxqjob, group->group.group_id, server->hostname, server->server_id, server->host_id);
 
     if (!res) {
         return 0;
@@ -1331,6 +1367,8 @@ void server_close(struct mxq_server *server)
         unlink(server->pidfilename);
 
     mx_funlock(server->flock);
+
+    mx_free_null(server->boot_id);
 }
 
 int killall(struct mxq_server *server, int sig, unsigned int pgrp)
@@ -1673,6 +1711,7 @@ int main(int argc, char *argv[])
     mx_log_info("  by Marius Tolzmann <tolzmann@molgen.mpg.de> " MXQ_VERSIONDATE);
     mx_log_info("  Max Planck Institute for Molecular Genetics - Berlin Dahlem");
     mx_log_info("hostname=%s server_id=%s :: MXQ server started.", server.hostname, server.server_id);
+    mx_log_info("  host_id=%s", server.host_id);
     mx_log_info("slots=%lu memory_total=%lu memory_avg_per_slot=%.0Lf memory_max_per_slot=%ld :: server initialized.",
                   server.slots, server.memory_total, server.memory_avg_per_slot, server.memory_max_per_slot);
 
diff --git a/mxqd.h b/mxqd.h
index 5a9ca86..c4db22d 100644
--- a/mxqd.h
+++ b/mxqd.h
@@ -70,12 +70,18 @@ struct mxq_server {
 
     struct mx_mysql *mysql;
 
+    char *boot_id;
+    unsigned long long int starttime;
+    char *host_id;
     char *hostname;
     char *server_id;
     char *lockfilename;
     char *pidfilename;
     struct mx_flock *flock;
 
+    char *initial_path;
+    char *initial_tmpdir;
+
     int is_running;
 };
 
diff --git a/mxqsub.c b/mxqsub.c
index 6cb34d6..87a476b 100644
--- a/mxqsub.c
+++ b/mxqsub.c
@@ -82,15 +82,18 @@ static void print_usage(void)
     "               to specify years, weeks, days, hours and minutes\n"
     "               Defaults to minutes if no suffix is set.\n"
     "\n"
+    "      --max-jobs-per-node=NUMBER  limit the number of jobs executed on each cluster node\n"
+    "                                  (default: 0 [limited by the server])\n"
+    "\n"
     "Job handling:\n"
     "  Define what to do if something bad happens:\n"
     "\n"
     "  -r, --restart[=MODE]  restart job on system failure (default: 'never')\n"
     "\n"
     "  available restart [MODE]s:\n"
-    "      'never'     do not restart\n"
+    "      'never'     do not restart. (default)\n"
     "      'samehost'  only restart if running on the same host.\n"
-    "      'always'    always restart or requeue. (default)\n"
+    "      'always'    always restart or requeue.\n"
     "\n"
     "Job grouping:\n"
     "  Grouping is done by default based on the jobs resource\n"
@@ -170,6 +173,7 @@ static int load_group_id(struct mx_mysql *mysql, struct mxq_group *g)
                 " AND job_threads = ?"
                 " AND job_memory = ?"
                 " AND job_time = ?"
+                " AND job_max_per_node = ?"
                 " AND group_priority = ?"
                 " AND group_status = 0"
                 " AND group_flags & ? = 0 "
@@ -189,8 +193,9 @@ static int load_group_id(struct mx_mysql *mysql, struct mxq_group *g)
     res += mx_mysql_statement_param_bind(stmt, 6, uint16, &(g->job_threads));
     res += mx_mysql_statement_param_bind(stmt, 7, uint64, &(g->job_memory));
     res += mx_mysql_statement_param_bind(stmt, 8, uint32, &(g->job_time));
-    res += mx_mysql_statement_param_bind(stmt, 9, uint16, &(g->group_priority));
-    res += mx_mysql_statement_param_bind(stmt, 10, uint64, &(flags));
+    res += mx_mysql_statement_param_bind(stmt, 9, uint16, &(g->job_max_per_node));
+    res += mx_mysql_statement_param_bind(stmt, 10, uint16, &(g->group_priority));
+    res += mx_mysql_statement_param_bind(stmt, 11, uint64, &(flags));
     assert(res == 0);
 
     res = mx_mysql_statement_execute(stmt, &num_rows);
@@ -250,6 +255,7 @@ static int load_group_id_by_group_id(struct mx_mysql *mysql, struct mxq_group *g
                 " AND job_threads = ?"
                 " AND job_memory = ?"
                 " AND job_time = ?"
+                " AND job_max_per_node = ?"
                 " AND group_priority = ?"
                 " AND group_status = 0"
                 " AND group_id = ?"
@@ -270,9 +276,10 @@ static int load_group_id_by_group_id(struct mx_mysql *mysql, struct mxq_group *g
     res += mx_mysql_statement_param_bind(stmt,  6, uint16, &(g->job_threads));
     res += mx_mysql_statement_param_bind(stmt,  7, uint64, &(g->job_memory));
     res += mx_mysql_statement_param_bind(stmt,  8, uint32, &(g->job_time));
-    res += mx_mysql_statement_param_bind(stmt,  9, uint16, &(g->group_priority));
-    res += mx_mysql_statement_param_bind(stmt, 10, uint64, &(g->group_id));
-    res += mx_mysql_statement_param_bind(stmt, 11, uint64, &(flags));
+    res += mx_mysql_statement_param_bind(stmt,  9, uint16, &(g->job_max_per_node));
+    res += mx_mysql_statement_param_bind(stmt, 10, uint16, &(g->group_priority));
+    res += mx_mysql_statement_param_bind(stmt, 11, uint64, &(g->group_id));
+    res += mx_mysql_statement_param_bind(stmt, 12, uint64, &(flags));
     assert(res == 0);
 
     res = mx_mysql_statement_execute(stmt, &num_rows);
@@ -325,6 +332,7 @@ static int load_group_id_run_or_wait(struct mx_mysql *mysql, struct mxq_group *g
                 " AND job_threads = ?"
                 " AND job_memory = ?"
                 " AND job_time = ?"
+                " AND job_max_per_node = ?"
                 " AND group_priority = ?"
                 " AND group_status = 0"
                 " AND ("
@@ -349,8 +357,9 @@ static int load_group_id_run_or_wait(struct mx_mysql *mysql, struct mxq_group *g
     res += mx_mysql_statement_param_bind(stmt, 6, uint16, &(g->job_threads));
     res += mx_mysql_statement_param_bind(stmt, 7, uint64, &(g->job_memory));
     res += mx_mysql_statement_param_bind(stmt, 8, uint32, &(g->job_time));
-    res += mx_mysql_statement_param_bind(stmt, 9, uint16, &(g->group_priority));
-    res += mx_mysql_statement_param_bind(stmt, 10, uint64, &(flags));
+    res += mx_mysql_statement_param_bind(stmt, 9, uint16, &(g->job_max_per_node));
+    res += mx_mysql_statement_param_bind(stmt, 10, uint16, &(g->group_priority));
+    res += mx_mysql_statement_param_bind(stmt, 11, uint64, &(flags));
     assert(res == 0);
 
     res = mx_mysql_statement_execute(stmt, &num_rows);
@@ -405,6 +414,7 @@ static int add_group(struct mx_mysql *mysql, struct mxq_group *g)
                 " job_threads = ?,"
                 " job_memory = ?,"
                 " job_time = ?,"
+                " job_max_per_node = ?,"
                 " group_priority = ?");
     if (!stmt) {
         mx_log_err("mx_mysql_statement_prepare(): %m");
@@ -420,7 +430,8 @@ static int add_group(struct mx_mysql *mysql, struct mxq_group *g)
     res += mx_mysql_statement_param_bind(stmt, 6, uint16, &(g->job_threads));
     res += mx_mysql_statement_param_bind(stmt, 7, uint64, &(g->job_memory));
     res += mx_mysql_statement_param_bind(stmt, 8, uint32, &(g->job_time));
-    res += mx_mysql_statement_param_bind(stmt, 9, uint16, &(g->group_priority));
+    res += mx_mysql_statement_param_bind(stmt, 9, uint16, &(g->job_max_per_node));
+    res += mx_mysql_statement_param_bind(stmt, 10, uint16, &(g->group_priority));
     assert(res == 0);
 
     res = mx_mysql_statement_execute(stmt, &num_rows);
@@ -585,6 +596,7 @@ int main(int argc, char *argv[])
     u_int16_t  arg_threads;
     u_int64_t  arg_memory;
     u_int32_t  arg_time;
+    u_int16_t  arg_max_per_node;
     u_int64_t  arg_groupid;
     char      *arg_workdir;
     char      *arg_stdout;
@@ -646,6 +658,8 @@ int main(int argc, char *argv[])
                 MX_OPTION_REQUIRED_ARG("memory",       'm'),
                 MX_OPTION_REQUIRED_ARG("runtime",      't'),
 
+                MX_OPTION_REQUIRED_ARG("max-jobs-per-node", 6),
+
                 MX_OPTION_REQUIRED_ARG("define",       'D'),
 
                 MX_OPTION_OPTIONAL_ARG("mysql-default-file",  'M'),
@@ -666,6 +680,7 @@ int main(int argc, char *argv[])
     arg_threads        = 1;
     arg_memory         = 2048;
     arg_time           = 0;
+    arg_max_per_node   = 0;
     arg_workdir        = current_workdir;
     arg_stdout         = "/dev/null";
     arg_stderr         = "stdout";
@@ -829,6 +844,13 @@ int main(int argc, char *argv[])
                 }
                 break;
 
+            case 6:
+                if (mx_strtou16(optctl.optarg, &arg_max_per_node) < 0) {
+                    mx_log_crit("--max-jobs-per-node '%s': %m", optctl.optarg);
+                    exit(EX_CONFIG);
+                }
+                break;
+
             case 'w':
                 if (!(*optctl.optarg)) {
                     mx_log_crit("--workdir '%s': String is empty.", optctl.optarg);
@@ -944,6 +966,8 @@ int main(int argc, char *argv[])
     group.job_memory     = arg_memory;
     group.job_time       = arg_time;
 
+    group.job_max_per_node = arg_max_per_node;
+
     job.job_flags      = arg_jobflags;
     job.job_priority   = arg_priority;
     job.job_workdir    = arg_workdir;
diff --git a/mysql/alter_tables_0.15.0.sql b/mysql/alter_tables_0.15.0.sql
new file mode 100644
index 0000000..95faa89
--- /dev/null
+++ b/mysql/alter_tables_0.15.0.sql
@@ -0,0 +1,11 @@
+ALTER TABLE mxq_group
+    ADD COLUMN
+        job_max_per_node  INT2 UNSIGNED NOT NULL DEFAULT 0
+    AFTER
+        job_time;
+
+ALTER TABLE mxq_job
+    ADD COLUMN
+        host_id VARCHAR(1023) NOT NULL DEFAULT ""
+    AFTER
+        server_id;
diff --git a/mysql/create_tables.sql b/mysql/create_tables.sql
index 30365ce..c203fa2 100644
--- a/mysql/create_tables.sql
+++ b/mysql/create_tables.sql
@@ -18,6 +18,8 @@ CREATE TABLE IF NOT EXISTS mxq_group (
    job_memory     INT8 UNSIGNED NOT NULL DEFAULT 1024,
    job_time       INT4 UNSIGNED NOT NULL DEFAULT 15,
 
+   job_max_per_node     INT2 UNSIGNED NOT NULL DEFAULT 0,
+
    group_jobs           INT8 UNSIGNED NOT NULL DEFAULT 0,
    group_jobs_inq       INT8 UNSIGNED NOT NULL DEFAULT 0,
    group_jobs_running   INT8 UNSIGNED NOT NULL DEFAULT 0,
@@ -81,6 +83,7 @@ CREATE TABLE IF NOT EXISTS mxq_job (
    host_submit    VARCHAR(64)     NOT NULL DEFAULT "localhost",
 
    server_id      VARCHAR(1023)   NOT NULL DEFAULT "",
+   host_id        VARCHAR(1023)   NOT NULL DEFAULT "",
 
    host_hostname  VARCHAR(64)     NOT NULL DEFAULT "",
    host_pid       INT4 UNSIGNED   NOT NULL DEFAULT 0,
diff --git a/mysql/fix_host_id.sql b/mysql/fix_host_id.sql
new file mode 100644
index 0000000..c3c6fa3
--- /dev/null
+++ b/mysql/fix_host_id.sql
@@ -0,0 +1,7 @@
+UPDATE mxq_job
+    SET
+        host_id = CONCAT(host_hostname, '::', server_id)
+    WHERE
+        host_id = ""
+    AND server_id != ""
+    AND host_hostname != "";
diff --git a/test_mx_util.c b/test_mx_util.c
index 66dd776..5700e80 100644
--- a/test_mx_util.c
+++ b/test_mx_util.c
@@ -1,7 +1,11 @@
 
+#define _GNU_SOURCE
+
 #include <assert.h>
 #include <errno.h>
 #include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
 
 #include "mx_util.h"
 
@@ -44,6 +48,7 @@ static void test_mx_strtoul(void)
     assert(mx_strtoul("-1", &l) == -ERANGE);
     assert(mx_strtoul(" -1", &l) == -ERANGE);
 
+    assert(mx_strtoul("123 123", &l) == -EINVAL);
     assert(mx_strtoul("123s", &l) == -EINVAL);
     assert(mx_strtoul("0888", &l) == -EINVAL);
     assert(mx_strtoul("1.2", &l)  == -EINVAL);
@@ -271,6 +276,87 @@ static void test_mx_strtobytes(void)
     assert(mx_strtobytes("test", &l) == -EINVAL);
 }
 
+static void test_mx_read_first_line_from_file(void)
+{
+    char *str;
+    long long int l;
+
+    assert(mx_read_first_line_from_file("/proc/sys/kernel/random/boot_id", &str) == 36);
+    assert(str);
+    mx_free_null(str);
+
+    assert(mx_read_first_line_from_file("/proc/sys/kernel/random/uuid", &str) == 36);
+    assert(str);
+    mx_free_null(str);
+
+    assert(mx_read_first_line_from_file("/proc/no_such_file", &str) == -ENOENT);
+    assert(str == NULL);
+
+    assert(mx_read_first_line_from_file("/proc/self/stat", &str) > 0);
+    assert(str);
+    mx_strtoll(str, &l);
+    mx_free_null(str);
+}
+
+static void test_mx_strscan(void)
+{
+    _mx_cleanup_free_ char *s = NULL;
+    char *str;
+    unsigned long long int ull;
+    long long int ll;
+    _mx_cleanup_free_ char *line = NULL;
+    struct proc_pid_stat pps = {0};
+    struct proc_pid_stat pps2 = {0};
+
+    assert(s = strdup("123 456 -789 246 abc"));
+    str = s;
+
+    assert(mx_strscan_ull(&str, &ull) == 0);
+    assert(ull == 123);
+
+    assert(mx_strscan_ull(&str, &ull) == 0);
+    assert(ull == 456);
+
+    assert(mx_strscan_ull(&str, &ull) == -ERANGE);
+    assert(mx_streq(str, "-789 246 abc"));
+
+    assert(mx_strscan_ll(&str, &ll) == 0);
+    assert(ll == -789);
+    assert(mx_streq(str, "246 abc"));
+
+    assert(mx_strscan_ll(&str, &ll) == 0);
+    assert(ll == 246);
+    assert(mx_streq(str, "abc"));
+
+    assert(mx_strscan_ull(&str, &ull) == -EINVAL);
+    assert(mx_streq(str, "abc"));
+    assert(mx_streq(s, "123 456 -789 246 abc"));
+    mx_free_null(s);
+
+    assert(s = strdup("123"));
+    str = s;
+    assert(mx_strscan_ull(&str, &ull) == 0);
+    assert(ull == 123);
+    assert(mx_streq(str, ""));
+    assert(mx_streq(s, "123"));
+
+    assert(mx_read_first_line_from_file("/proc/self/stat", &line) > 0);
+    assert(mx_strscan_proc_pid_stat(line, &pps) == 0);
+    assert(pps.pid == getpid());
+    assert(pps.ppid == getppid());
+    assert(pps.state == 'R');
+    assert(mx_streq(pps.comm, program_invocation_short_name) || mx_streq(pps.comm, "memcheck-amd64-"));
+    mx_proc_pid_stat_free(&pps);
+
+    assert(mx_proc_pid_stat(&pps2, getpid()) == 0);
+    assert(pps2.pid == getpid());
+    assert(pps2.ppid == getppid());
+    assert(pps2.state == 'R');
+    assert(mx_streq(pps2.comm, program_invocation_short_name) || mx_streq(pps2.comm, "memcheck-amd64-"));
+    mx_proc_pid_stat_free(&pps2);
+}
+
+
 int main(int argc, char *argv[])
 {
     test_mx_strskipwhitespaces();
@@ -284,5 +370,7 @@ int main(int argc, char *argv[])
     test_mx_strtoseconds();
     test_mx_strtominutes();
     test_mx_strtobytes();
+    test_mx_read_first_line_from_file();
+    test_mx_strscan();
     return 0;
 }
diff --git a/web/lighttpd.conf.in b/web/lighttpd.conf.in
index 7013003..6ac7210 100644
--- a/web/lighttpd.conf.in
+++ b/web/lighttpd.conf.in
@@ -1,7 +1,7 @@
 # lighttpd.conf
 
 # start with:
-#   lighttpd [-D] -f @LIBEXECDIR@/mxq/lighttpd.conf
+#   lighttpd [-D] -f @SYSCONFDIR@/mxq/lighttpd.conf
 
 server.document-root = "@CGIDIR@"
 
diff --git a/web/pages/mxq/mxq.in b/web/pages/mxq/mxq.in
index 5e75c6a..d90ca06 100755
--- a/web/pages/mxq/mxq.in
+++ b/web/pages/mxq/mxq.in
@@ -232,9 +232,12 @@ sub group_detail {
 
 	my $group_status_text=group_status($o{'group_status'});
 
+	my $group_name=escapeHTML($o{group_name});
+	my $job_command=escapeHTML($o{job_command});
+
 	$out.=<<"EOF";
 <pre>
-group_name     : $o{group_name}
+group_name     : $group_name
 group_status   : $group_status_text
 group_flags    : $o{group_flags}
 group_priority : $o{group_priority}
@@ -244,11 +247,13 @@ user_name      : $o{user_name}
 user_gid       : $o{user_gid}
 user_group     : $o{user_group}
 
-job_command    : $o{job_command}
+job_command    : $job_command
 job_threads    : $o{job_threads}
 job_memory     : $o{job_memory}
 job_time       : $o{job_time}
 
+job_max_per_node      : $o{job_max_per_node}
+
 group_jobs            : $o{group_jobs}
 group_jobs_inq        : $o{group_jobs_inq}
 group_jobs_running    : $o{group_jobs_running}
@@ -315,9 +320,15 @@ sub job {
 	my $job_status_text=job_status($o{'job_status'});
 	my $job_umask_text=sprintf('%03O',$o{job_umask});
 	my $link_group_id=a({href=>selfurl("/group/$o{group_id}")},$o{group_id});
-	my $argv=split_cmd($o{job_argv});
+	my $job_argv=escapeHTML(split_cmd($o{job_argv}));
+	my $job_workdir=escapeHTML($o{job_workdir});
+	my $job_stdout=escapeHTML($o{job_stdout});
+	my $job_stderr=escapeHTML($o{job_stderr});
+
+	defined $_ or $_='&lt;null&gt;' for values %o;
 
 	$out.=h2("Job Details $o{job_id}");
+
 	$out.=<<"EOF";
 <pre>
 job_status       : $job_status_text
@@ -326,16 +337,17 @@ job_priority     : $o{job_priority}
 
 group_id         : $link_group_id
 
-job_workdir      : $o{job_workdir}
-job_argc         ; $o{job_argc}
-job_argv         ; $argv
-job_stdout       : $o{job_stdout}
-job_stderr       : $o{job_stderr}
+job_workdir      : $job_workdir
+job_argc         : $o{job_argc}
+job_argv         : $job_argv
+job_stdout       : $job_stdout
+job_stderr       : $job_stderr
 job_umask:       : $job_umask_text
 
 host_submit      : $o{host_submit}
 
 server_id        : $o{server_id}
+host_id          : $o{host_id}
 
 host_hostname    : $o{host_hostname}
 host_pid         : $o{host_pid}
@@ -343,7 +355,7 @@ host_slots       : $o{host_slots}
 
 date_submit      : $o{date_submit}
 date_start       : $o{date_start}
-date_end         : $o{date_start}
+date_end         : $o{date_end}
 
 job_id_new       : $o{job_id_new}
 job_id_old       : $o{job_id_old}
@@ -351,9 +363,9 @@ job_id_first     : $o{job_id_first}
 
 stats_status     : $o{stats_status}
 
-stats_utim_sec   : $o{stats_utime_sec}
+stats_utime_sec  : $o{stats_utime_sec}
 stats_utime_usec : $o{stats_utime_usec}
-stats_stim_sec   : $o{stats_stime_sec}
+stats_stime_sec  : $o{stats_stime_sec}
 stats_stime_usec : $o{stats_stime_usec}
 stats_real_sec   : $o{stats_real_sec}
 stats_real_usec  : $o{stats_real_usec}