diff --git a/include/common/standard.h b/include/common/standard.h
index be719f714..682711105 100644
--- a/include/common/standard.h
+++ b/include/common/standard.h
@@ -259,12 +259,19 @@ unsigned int round_2dig(unsigned int i);
 extern const char *invalid_char(const char *name);
 
 /*
- * Checks <domainname> for invalid characters. Valid chars are [A-Za-z0-9_.-].
+ * Checks <name> for invalid characters. Valid chars are [A-Za-z0-9_.-].
  * If an invalid character is found, a pointer to it is returned.
  * If everything is fine, NULL is returned.
  */
 extern const char *invalid_domainchar(const char *name);
 
+/*
+ * Checks <name> for invalid characters. Valid chars are [A-Za-z_.-].
+ * If an invalid character is found, a pointer to it is returned.
+ * If everything is fine, NULL is returned.
+ */
+extern const char *invalid_prefix_char(const char *name);
+
 /*
  * converts <str> to a locally allocated struct sockaddr_storage *, and a
  * port range consisting in two integers. The low and high end are always set
diff --git a/include/types/server.h b/include/types/server.h
index bfaa94115..8d68dcb56 100644
--- a/include/types/server.h
+++ b/include/types/server.h
@@ -274,6 +274,15 @@ struct server {
 		int line;			/* line where the section appears */
 		struct eb32_node id;		/* place in the tree of used IDs */
 	} conf;					/* config information */
+	/* Template information used only for server objects which
+	 * serve as template filled at parsing time and used during
+	 * server allocations from server templates.
+	 */
+	struct {
+		char *prefix;
+		int nb_low;
+		int nb_high;
+	} tmpl_info;
 };
 
 /* Descriptor for a "server" keyword. The ->parse() function returns 0 in case of
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 348b9e886..d44949a7d 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -2859,7 +2859,9 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
 	curproxy->conf.args.line = linenum;
 
 	/* Now let's parse the proxy-specific keywords */
-	if (!strcmp(args[0], "server") || !strcmp(args[0], "default-server")) {
+	if (!strcmp(args[0], "server")         ||
+	    !strcmp(args[0], "default-server") ||
+	    !strcmp(args[0], "server-template")) {
 		err_code |= parse_server(file, linenum, args, curproxy, &defproxy);
 		if (err_code & ERR_FATAL)
 			goto out;
diff --git a/src/server.c b/src/server.c
index f25a7a000..9f0778874 100644
--- a/src/server.c
+++ b/src/server.c
@@ -1969,18 +1969,53 @@ static int server_finalize_init(const char *file, int linenum, char **args, int
 	return 0;
 }
 
+/*
+ * Parse as much as possible such a range string argument: low[-high]
+ * Set <nb_low> and <nb_high> values so that they may be reused by this loop
+ * for(int i = nb_low; i <= nb_high; i++)... with nb_low >= 1.
+ * Fails if 'low' < 0 or 'high' is present and not higher than 'low'.
+ * Returns 0 if succeeded, -1 if not.
+ */
+static int srv_tmpl_parse_range(struct server *srv, const char *arg, int *nb_low, int *nb_high)
+{
+	char *nb_high_arg;
+
+	*nb_high = 0;
+	chunk_printf(&trash, "%s", arg);
+	*nb_low = atoi(trash.str);
+
+	if ((nb_high_arg = strchr(trash.str, '-'))) {
+		*nb_high_arg++ = '\0';
+		*nb_high = atoi(nb_high_arg);
+	}
+	else {
+		*nb_high += *nb_low;
+		*nb_low = 1;
+	}
+
+	if (*nb_low < 0 || *nb_high < *nb_low)
+		return -1;
+
+	return 0;
+}
+
 int parse_server(const char *file, int linenum, char **args, struct proxy *curproxy, struct proxy *defproxy)
 {
 	struct server *newsrv = NULL;
-	const char *err;
+	const char *err = NULL;
 	char *errmsg = NULL;
 	int err_code = 0;
 	unsigned val;
 	char *fqdn = NULL;
 
-	if (!strcmp(args[0], "server") || !strcmp(args[0], "default-server")) {  /* server address */
+	if (!strcmp(args[0], "server")         ||
+	    !strcmp(args[0], "default-server") ||
+	    !strcmp(args[0], "server-template")) {
 		int cur_arg;
 		int defsrv = (*args[0] == 'd');
+		int srv = !defsrv && !strcmp(args[0], "server");
+		int srv_tmpl = !defsrv && !srv;
+		int tmpl_range_low = 0, tmpl_range_high = 0;
 
 		if (!defsrv && curproxy == defproxy) {
 			Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]);
@@ -1990,19 +2025,47 @@ int parse_server(const char *file, int linenum, char **args, struct proxy *curpr
 		else if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], NULL))
 			err_code |= ERR_ALERT | ERR_FATAL;
 
-		if (!defsrv && !*args[2]) {
-			Alert("parsing [%s:%d] : '%s' expects <name> and <addr>[:<port>] as arguments.\n",
-			      file, linenum, args[0]);
+		/* There is no mandatory first arguments for default server. */
+		if (srv) {
+			if (!*args[2]) {
+				/* 'server' line number of argument check. */
+				Alert("parsing [%s:%d] : '%s' expects <name> and <addr>[:<port>] as arguments.\n",
+					  file, linenum, args[0]);
+				err_code |= ERR_ALERT | ERR_FATAL;
+				goto out;
+			}
+
+			err = invalid_char(args[1]);
+		}
+		else if (srv_tmpl) {
+			if (!*args[3]) {
+				/* 'server-template' line number of argument check. */
+				Alert("parsing [%s:%d] : '%s' expects <prefix> <nb | range> <addr>[:<port>] as arguments.\n",
+					  file, linenum, args[0]);
+				err_code |= ERR_ALERT | ERR_FATAL;
+				goto out;
+			}
+
+			err = invalid_prefix_char(args[1]);
+		}
+
+		if (err) {
+			Alert("parsing [%s:%d] : character '%c' is not permitted in %s %s '%s'.\n",
+			      file, linenum, *err, args[0], srv ? "name" : "prefix", args[1]);
 			err_code |= ERR_ALERT | ERR_FATAL;
 			goto out;
 		}
 
-		err = invalid_char(args[1]);
-		if (err && !defsrv) {
-			Alert("parsing [%s:%d] : character '%c' is not permitted in server name '%s'.\n",
-			      file, linenum, *err, args[1]);
-			err_code |= ERR_ALERT | ERR_FATAL;
-			goto out;
+		cur_arg = 2;
+		if (srv_tmpl) {
+			/* Parse server-template <nb | range> arg. */
+			if (srv_tmpl_parse_range(newsrv, args[cur_arg], &tmpl_range_low, &tmpl_range_high) < 0) {
+				Alert("parsing [%s:%d] : Wrong %s number or range arg '%s'.\n",
+					  file, linenum, args[0], args[cur_arg]);
+				err_code |= ERR_ALERT | ERR_FATAL;
+				goto out;
+			}
+			cur_arg++;
 		}
 
 		if (!defsrv) {
@@ -2018,12 +2081,24 @@ int parse_server(const char *file, int linenum, char **args, struct proxy *curpr
 				goto out;
 			}
 
+			if (srv_tmpl) {
+				newsrv->tmpl_info.nb_low = tmpl_range_low;
+				newsrv->tmpl_info.nb_high = tmpl_range_high;
+			}
+
 			/* the servers are linked backwards first */
 			newsrv->next = curproxy->srv;
 			curproxy->srv = newsrv;
 			newsrv->conf.file = strdup(file);
 			newsrv->conf.line = linenum;
-			newsrv->id = strdup(args[1]);
+			/* Note: for a server template, its id is its prefix.
+			 * This is a temporary id which will be used for server allocations to come
+			 * after parsing.
+			 */
+			if (srv)
+				newsrv->id = strdup(args[1]);
+			else
+				newsrv->tmpl_info.prefix = strdup(args[1]);
 
 			/* several ways to check the port component :
 			 *  - IP    => port=+0, relative (IPv4 only)
@@ -2032,7 +2107,7 @@ int parse_server(const char *file, int linenum, char **args, struct proxy *curpr
 			 *  - IP:+N => port=+N, relative
 			 *  - IP:-N => port=-N, relative
 			 */
-			sk = str2sa_range(args[2], &port, &port1, &port2, &errmsg, NULL, &fqdn, 0);
+			sk = str2sa_range(args[cur_arg], &port, &port1, &port2, &errmsg, NULL, &fqdn, 0);
 			if (!sk) {
 				Alert("parsing [%s:%d] : '%s %s' : %s\n", file, linenum, args[0], args[1], errmsg);
 				err_code |= ERR_ALERT | ERR_FATAL;
@@ -2073,7 +2148,7 @@ int parse_server(const char *file, int linenum, char **args, struct proxy *curpr
 				goto skip_name_resolution;
 			if ((dns_str_to_dn_label(newsrv->hostname, curr_resolution->hostname_dn, curr_resolution->hostname_dn_len + 1)) == NULL) {
 				Alert("parsing [%s:%d] : Invalid hostname '%s'\n",
-				      file, linenum, args[2]);
+				      file, linenum, args[cur_arg]);
 				err_code |= ERR_ALERT | ERR_FATAL;
 				goto out;
 			}
@@ -2093,14 +2168,14 @@ int parse_server(const char *file, int linenum, char **args, struct proxy *curpr
 
 			if (!newsrv->hostname && !protocol_by_family(newsrv->addr.ss_family)) {
 				Alert("parsing [%s:%d] : Unknown protocol family %d '%s'\n",
-				      file, linenum, newsrv->addr.ss_family, args[2]);
+				      file, linenum, newsrv->addr.ss_family, args[cur_arg]);
 				err_code |= ERR_ALERT | ERR_FATAL;
 				goto out;
 			}
 
 			/* Copy default server settings to new server settings. */
 			srv_settings_cpy(newsrv, &curproxy->defsrv);
-			cur_arg = 3;
+			cur_arg++;
 		} else {
 			newsrv = &curproxy->defsrv;
 			cur_arg = 1;
diff --git a/src/standard.c b/src/standard.c
index 99f7066bb..6abedb4a2 100644
--- a/src/standard.c
+++ b/src/standard.c
@@ -592,17 +592,18 @@ const char *invalid_char(const char *name)
 }
 
 /*
- * Checks <domainname> for invalid characters. Valid chars are [A-Za-z0-9_.-].
+ * Checks <name> for invalid characters. Valid chars are [_.-] and those
+ * accepted by <f> function.
  * If an invalid character is found, a pointer to it is returned.
  * If everything is fine, NULL is returned.
  */
-const char *invalid_domainchar(const char *name) {
+static inline const char *__invalid_char(const char *name, int (*f)(int)) {
 
 	if (!*name)
 		return name;
 
 	while (*name) {
-		if (!isalnum((int)(unsigned char)*name) && *name != '.' &&
+		if (!f((int)(unsigned char)*name) && *name != '.' &&
 		    *name != '_' && *name != '-')
 			return name;
 
@@ -612,6 +613,24 @@ const char *invalid_domainchar(const char *name) {
 	return NULL;
 }
 
+/*
+ * Checks <name> for invalid characters. Valid chars are [A-Za-z0-9_.-].
+ * If an invalid character is found, a pointer to it is returned.
+ * If everything is fine, NULL is returned.
+ */
+const char *invalid_domainchar(const char *name) {
+	return __invalid_char(name, isalnum);
+}
+
+/*
+ * Checks <name> for invalid characters. Valid chars are [A-Za-z_.-].
+ * If an invalid character is found, a pointer to it is returned.
+ * If everything is fine, NULL is returned.
+ */
+const char *invalid_prefix_char(const char *name) {
+	return __invalid_char(name, isalpha);
+}
+
 /*
  * converts <str> to a struct sockaddr_storage* provided by the caller. The
  * caller must have zeroed <sa> first, and may have set sa->ss_family to force