Repository: avenga/couper
Pull request #83 (2020-12-30 16:22:06)

Refactor configuration load and prepare related merges on hcl level


Patch:
diff --git a/command/cmd.go b/command/cmd.go
index 1345a799..1316f937 100644
--- a/command/cmd.go
+++ b/command/cmd.go
@@ -10,7 +10,7 @@ import (
 )
 
 type Cmd interface {
-	Execute(args Args, config *config.Gateway, logger *logrus.Entry) error
+	Execute(args Args, config *config.CouperFile, logger *logrus.Entry) error
 	Usage() string
 }
 
diff --git a/command/run.go b/command/run.go
index 5a261efe..508144bf 100644
--- a/command/run.go
+++ b/command/run.go
@@ -24,29 +24,29 @@ func NewRun(ctx context.Context) *Run {
 	return &Run{context: ctx}
 }
 
-func (r Run) Execute(args Args, config *config.Gateway, logEntry *logrus.Entry) error {
-	httpConf := runtime.NewHTTPConfig(config)
-
+func (r Run) Execute(args Args, config *config.CouperFile, logEntry *logrus.Entry) error {
 	// TODO: Extract and execute flagSet & env handling in a more generic way for future commands.
 	set := flag.NewFlagSet("settings", flag.ContinueOnError)
-	set.StringVar(&httpConf.HealthPath, "health-path", httpConf.HealthPath, "-health-path /healthz")
-	set.IntVar(&httpConf.ListenPort, "p", httpConf.ListenPort, "-p 8080")
-	set.BoolVar(&httpConf.UseXFH, "xfh", httpConf.UseXFH, "-xfh")
-	set.StringVar(&httpConf.RequestIDFormat, "request-id-format", httpConf.RequestIDFormat, "-request-id-format uuid4")
+	set.StringVar(&config.Settings.HealthPath, "health-path", config.Settings.HealthPath, "-health-path /healthz")
+	set.IntVar(&config.Settings.DefaultPort, "p", config.Settings.DefaultPort, "-p 8080")
+	set.BoolVar(&config.Settings.XForwardedHost, "xfh", config.Settings.XForwardedHost, "-xfh")
+	set.StringVar(&config.Settings.RequestIDFormat, "request-id-format", config.Settings.RequestIDFormat, "-request-id-format uuid4")
 	if err := set.Parse(args.Filter(set)); err != nil {
 		return err
 	}
-	envConf := &runtime.HTTPConfig{}
-	env.Decode(envConf)
-	httpConf = httpConf.Merge(envConf)
+
+	env.Decode(config.Settings)
+
+	timings := runtime.DefaultTimings
+	env.Decode(&timings)
 
 	// logEntry has still the 'daemon' type which can be used for config related load errors.
-	srvMux, err := runtime.NewServerConfiguration(config, httpConf, logEntry)
+	srvMux, err := runtime.NewServerConfiguration(config, logEntry)
 	if err != nil {
 		return err
 	}
 
-	serverList, listenCmdShutdown := server.NewServerList(r.context, logEntry.Logger, httpConf, srvMux)
+	serverList, listenCmdShutdown := server.NewServerList(r.context, logEntry.Logger, config.Settings, &timings, srvMux)
 	for _, srv := range serverList {
 		srv.Listen()
 	}
diff --git a/config/api.go b/config/api.go
index 69186124..2e650633 100644
--- a/config/api.go
+++ b/config/api.go
@@ -8,20 +8,24 @@ import (
 var _ Inline = &Api{}
 
 type Api struct {
-	AccessControl        []string    `hcl:"access_control,optional"`
-	CORS                 *CORS       `hcl:"cors,block"`
-	Backend              string      `hcl:"backend,optional"`
-	BasePath             string      `hcl:"base_path,optional"`
-	DisableAccessControl []string    `hcl:"disable_access_control,optional"`
-	Endpoint             []*Endpoint `hcl:"endpoint,block"`
-	ErrorFile            string      `hcl:"error_file,optional"`
-	Remain               hcl.Body    `hcl:",remain" json:"-"`
+	AccessControl        []string  `hcl:"access_control,optional"`
+	CORS                 *CORS     `hcl:"cors,block"`
+	Backend              string    `hcl:"backend,optional"`
+	BasePath             string    `hcl:"base_path,optional"`
+	DisableAccessControl []string  `hcl:"disable_access_control,optional"`
+	Endpoints            Endpoints `hcl:"endpoint,block"`
+	ErrorFile            string    `hcl:"error_file,optional"`
+	Remain               hcl.Body  `hcl:",remain" json:"-"`
 }
 
 func (a Api) Body() hcl.Body {
 	return a.Remain
 }
 
+func (a Api) Reference() string {
+	return a.Backend
+}
+
 func (a Api) Schema(inline bool) *hcl.BodySchema {
 	if !inline {
 		schema, _ := gohcl.ImpliedBodySchema(a)
diff --git a/config/backend.go b/config/backend.go
index 43a3de83..4680add8 100644
--- a/config/backend.go
+++ b/config/backend.go
@@ -6,22 +6,28 @@ import (
 	"github.com/zclconf/go-cty/cty"
 )
 
-var _ Inline = &Backend{}
+var (
+	_ Inline = &Backend{}
+)
 
 type Backend struct {
-	ConnectTimeout   string   `hcl:"connect_timeout,optional"`
-	Name             string   `hcl:"name,label"`
-	Remain           hcl.Body `hcl:",remain"`
-	RequestBodyLimit string   `hcl:"request_body_limit,optional"`
-	TTFBTimeout      string   `hcl:"ttfb_timeout,optional"`
-	Timeout          string   `hcl:"timeout,optional"`
-	OpenAPI          *OpenAPI `hcl:"openapi,block"`
+	ConnectTimeout   string     `hcl:"connect_timeout,optional"`
+	Name             string     `hcl:"name,label"`
+	Remain           hcl.Body   `hcl:",remain"`
+	RequestBodyLimit string     `hcl:"request_body_limit,optional"`
+	TTFBTimeout      string     `hcl:"ttfb_timeout,optional"`
+	Timeout          string     `hcl:"timeout,optional"`
+	OpenAPI          []*OpenAPI `hcl:"openapi,block"`
 }
 
 func (b Backend) Body() hcl.Body {
 	return b.Remain
 }
 
+func (b Backend) Reference() string {
+	return b.Name
+}
+
 func (b Backend) Schema(inline bool) *hcl.BodySchema {
 	schema, _ := gohcl.ImpliedBodySchema(b)
 	if !inline {
@@ -41,59 +47,10 @@ func (b Backend) Schema(inline bool) *hcl.BodySchema {
 		SetQueryParams     map[string]cty.Value `hcl:"set_query_params,optional"`
 	}
 
-	// TODO: handle block parsing on config load first
-	blocks := schema.Blocks
 	schema, _ = gohcl.ImpliedBodySchema(&Inline{})
-	schema.Blocks = blocks
 	return schema
 }
 
-// Merge overrides the left backend configuration and returns a new instance.
-func (b *Backend) Merge(other *Backend) (*Backend, []hcl.Body) {
-	if b == nil || other == nil {
-		return nil, nil
-	}
-
-	var bodies []hcl.Body
-
-	result := *b
-
-	if other.Name != "" {
-		result.Name = other.Name
-	}
-
-	if result.Remain != nil {
-		bodies = append(bodies, result.Remain)
-	}
-
-	if other.Remain != nil {
-		bodies = append(bodies, other.Remain)
-		result.Remain = other.Remain
-	}
-
-	if other.ConnectTimeout != "" {
-		result.ConnectTimeout = other.ConnectTimeout
-	}
-
-	if other.RequestBodyLimit != "" {
-		result.RequestBodyLimit = other.RequestBodyLimit
-	}
-
-	if other.TTFBTimeout != "" {
-		result.TTFBTimeout = other.TTFBTimeout
-	}
-
-	if other.Timeout != "" {
-		result.Timeout = other.Timeout
-	}
-
-	if other.OpenAPI != nil {
-		result.OpenAPI = other.OpenAPI
-	}
-
-	return &result, bodies
-}
-
 func newBackendSchema(schema *hcl.BodySchema, body hcl.Body) *hcl.BodySchema {
 	for i, block := range schema.Blocks {
 		// inline backend block MAY have no label
@@ -102,6 +59,7 @@ func newBackendSchema(schema *hcl.BodySchema, body hcl.Body) *hcl.BodySchema {
 			content, _, _ := body.PartialContent(schema)
 			if content == nil || len(content.Blocks) == 0 {
 				schema.Blocks[i].LabelNames = nil
+				break
 			}
 		}
 	}
diff --git a/config/body/body.go b/config/body/body.go
new file mode 100644
index 00000000..f19b22ad
--- /dev/null
+++ b/config/body/body.go
@@ -0,0 +1,39 @@
+package body
+
+import "github.com/hashicorp/hcl/v2"
+
+var _ hcl.Body = &Body{}
+
+type Attributes interface {
+	JustAllAttributes() []hcl.Attributes
+	JustAllAttributesWithName(string) []hcl.Attributes
+}
+
+type Body struct {
+	content *hcl.BodyContent
+}
+
+func New(content *hcl.BodyContent) hcl.Body {
+	return &Body{content}
+}
+
+func (e *Body) Content(_ *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
+	return e.content, nil
+}
+
+func (e *Body) PartialContent(_ *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
+	return e.content, e, nil
+}
+
+func (e *Body) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
+	attrs := hcl.Attributes{}
+	for k, v := range e.content.Attributes {
+		cv := *v
+		attrs[k] = &cv
+	}
+	return attrs, nil
+}
+
+func (e *Body) MissingItemRange() hcl.Range {
+	return e.content.MissingItemRange
+}
diff --git a/config/configload/load.go b/config/configload/load.go
new file mode 100644
index 00000000..f72918e3
--- /dev/null
+++ b/config/configload/load.go
@@ -0,0 +1,217 @@
+package configload
+
+import (
+	"fmt"
+	"io/ioutil"
+	"path/filepath"
+
+	"github.com/hashicorp/hcl/v2"
+	"github.com/hashicorp/hcl/v2/gohcl"
+
+	"github.com/avenga/couper/config"
+	"github.com/avenga/couper/config/body"
+	"github.com/avenga/couper/config/parser"
+	"github.com/avenga/couper/eval"
+)
+
+const (
+	backend     = "backend"
+	definitions = "definitions"
+	pathAttr    = "path"
+	server      = "server"
+	settings    = "settings"
+)
+
+func LoadFile(filePath string) (*config.CouperFile, error) {
+	_, err := config.SetWorkingDirectory(filePath)
+	if err != nil {
+		return nil, err
+	}
+	filename := filepath.Base(filePath)
+	src, err := ioutil.ReadFile(filename)
+	if err != nil {
+		return nil, fmt.Errorf("failed to load configuration: %w", err)
+	}
+	return LoadBytes(src, filename)
+}
+
+func LoadBytes(src []byte, filename string) (*config.CouperFile, error) {
+	hclBody, diags := parser.Load(src, filename)
+	if diags.HasErrors() {
+		return nil, diags
+	}
+
+	return LoadConfig(hclBody, src)
+}
+
+func LoadConfig(body hcl.Body, src []byte) (*config.CouperFile, error) {
+	defaults := config.DefaultSettings
+	file := &config.CouperFile{
+		Bytes:       src,
+		Context:     eval.NewENVContext(src),
+		Definitions: &config.Definitions{},
+		Settings:    &defaults,
+	}
+
+	fileSchema, _ := gohcl.ImpliedBodySchema(file)
+	content, diags := body.Content(fileSchema)
+	if content == nil {
+		return nil, fmt.Errorf("invalid configuration: %w", diags)
+	}
+
+	// reading possible reference definitions first. Those are the base for refinement merges during server block read out.
+	for _, outerBlock := range content.Blocks {
+		switch outerBlock.Type {
+		case definitions:
+			if diags = gohcl.DecodeBody(outerBlock.Body, file.Context, file.Definitions); diags.HasErrors() {
+				return nil, diags
+			}
+		case settings:
+			if diags = gohcl.DecodeBody(outerBlock.Body, file.Context, file.Settings); diags.HasErrors() {
+				return nil, diags
+			}
+		}
+	}
+
+	// reading per server block and merge backend settings which results in a final server configuration.
+	for _, serverBlock := range content.Blocks.OfType(server) {
+		srv := &config.Server{}
+		if diags = gohcl.DecodeBody(serverBlock.Body, file.Context, srv); diags.HasErrors() {
+			return nil, diags
+		}
+
+		if len(serverBlock.Labels) > 0 {
+			srv.Name = serverBlock.Labels[0]
+		}
+
+		file.Server = append(file.Server, srv)
+
+		// api block(s)
+		for _, apiBlock := range []*config.Api{srv.API} {
+			if apiBlock == nil {
+				continue
+			}
+
+			bodies, err := mergeBackendBodies(file.Definitions, apiBlock)
+			if err != nil {
+				return nil, err
+			}
+
+			// empty bodies would be removed with a hcl.Merge.. later on.
+			if err = refineEndpoints(file.Definitions, bodies, apiBlock.Endpoints); err != nil {
+				return nil, err
+			}
+		}
+
+		// standalone endpoints
+		// TODO: free endpoints
+		//if err := refineEndpoints(file.Definitions, nil, srv.Endpoints); err != nil {
+		//	return nil, err
+		//}
+	}
+
+	return file, nil
+}
+
+func mergeBackendBodies(definitions *config.Definitions, inlineBackend config.Inline) ([]hcl.Body, error) {
+	reference, err := definitions.BackendWithName(inlineBackend.Reference())
+	if err != nil {
+		return nil, err
+	}
+
+	content, _, diags := inlineBackend.Body().PartialContent(inlineBackend.Schema(true))
+	if diags.HasErrors() {
+		return nil, diags
+	}
+
+	var bodies []hcl.Body
+	var backends hcl.Blocks
+	if content != nil {
+		backends = content.Blocks.OfType(backend)
+	}
+
+	if reference != nil {
+		if content != nil && len(backends) > 0 {
+			return nil, fmt.Errorf("configuration error: inlineBackend reference and inline definition")
+		}
+		// we have a reference, append to list and...
+		bodies = append(bodies, reference.Remain)
+	}
+	// ...additionally add the inline overrides.
+	if content != nil && len(content.Attributes) > 0 {
+		bodies = append(bodies, body.New(&hcl.BodyContent{
+			Attributes:       content.Attributes,
+			MissingItemRange: content.MissingItemRange,
+		}))
+	}
+
+	if len(backends) > 0 {
+		if len(backends[0].Labels) > 0 {
+			reference, err = definitions.BackendWithName(backends[0].Labels[0])
+			if err != nil {
+				return nil, err
+			}
+			bodies = append(bodies, reference.Remain)
+		}
+
+		bodies = append(bodies, backends[0].Body)
+	}
+
+	return bodies, nil
+}
+
+func refineEndpoints(definitions *config.Definitions, parents []hcl.Body, endpoints config.Endpoints) error {
+	for e, endpoint := range endpoints {
+		merged, err := mergeBackendBodies(definitions, endpoint)
+		if err != nil {
+			return err
+		}
+
+		merged, err = appendPathAttribute(append(parents, merged...), endpoint)
+		if err != nil {
+			return err
+		}
+
+		endpoints[e].Remain = MergeBodies(merged)
+	}
+	return nil
+}
+
+// appendPathAttribute determines if the given endpoint has child definitions which relies on references
+// which 'path' attribute should be refined with the endpoints inline value.
+func appendPathAttribute(bodies []hcl.Body, endpoint *config.Endpoint) ([]hcl.Body, error) {
+	if len(bodies) == 0 || endpoint == nil {
+		return bodies, nil
+	}
+
+	ctnt, _, diags := endpoint.Body().PartialContent(endpoint.Schema(true))
+	if diags.HasErrors() {
+		return nil, diags
+	} else if ctnt == nil {
+		return bodies, nil
+	}
+
+	// do not override path with an endpoint one if a reference override or
+	// an inline definition contains an explicit path attribute value.
+	blocks := ctnt.Blocks.OfType(backend)
+	if len(blocks) > 0 {
+		bodyAttrs, _ := blocks[0].Body.JustAttributes()
+		for name := range bodyAttrs {
+			if name == pathAttr {
+				return bodies, nil
+			}
+		}
+	}
+
+	for name, attr := range ctnt.Attributes {
+		if name == pathAttr {
+			return append(bodies, body.New(&hcl.BodyContent{
+				Attributes: hcl.Attributes{
+					pathAttr: attr,
+				},
+			})), nil
+		}
+	}
+
+	return bodies, nil
+}
diff --git a/config/configload/merged.go b/config/configload/merged.go
new file mode 100644
index 00000000..3471e2bd
--- /dev/null
+++ b/config/configload/merged.go
@@ -0,0 +1,242 @@
+// File based on "github.com/hashicorp/hcl/v2/merged.go" except diagnostic errors for duplicates
+// since we explicitly want them and apply an override.
+
+package configload
+
+import (
+	"fmt"
+
+	"github.com/hashicorp/hcl/v2"
+)
+
+// reportDuplicates is the global switch to handle force merges.
+var reportDuplicates = false
+
+// MergeBodies is like MergeFiles except it deals directly with bodies, rather
+// than with entire files.
+func MergeBodies(bodies []hcl.Body) hcl.Body {
+	if len(bodies) == 0 {
+		// Swap out for our singleton empty body, to reduce the number of
+		// empty slices we have hanging around.
+		return EmptyBody()
+	}
+
+	// If any of the given bodies are already merged bodies, we'll unpack
+	// to flatten to a single mergedBodies, since that's conceptually simpler.
+	// This also, as a side-effect, eliminates any empty bodies, since
+	// empties are merged bodies with no inner bodies.
+	var newLen int
+	var flatten bool
+	for _, body := range bodies {
+		if children, merged := body.(mergedBodies); merged {
+			newLen += len(children)
+			flatten = true
+		} else {
+			newLen++
+		}
+	}
+
+	if !flatten { // not just newLen == len, because we might have mergedBodies with single bodies inside
+		return mergedBodies(bodies)
+	}
+
+	if newLen == 0 {
+		// Don't allocate a new empty when we already have one
+		return emptyBody
+	}
+
+	newBodies := make([]hcl.Body, 0, newLen)
+	for _, body := range bodies {
+		if children, merged := body.(mergedBodies); merged {
+			newBodies = append(newBodies, children...)
+		} else {
+			newBodies = append(newBodies, body)
+		}
+	}
+	return mergedBodies(newBodies)
+}
+
+var emptyBody = mergedBodies([]hcl.Body{})
+
+// EmptyBody returns a body with no content. This body can be used as a
+// placeholder when a body is required but no body content is available.
+func EmptyBody() hcl.Body {
+	return emptyBody
+}
+
+type mergedBodies []hcl.Body
+
+// Content returns the content produced by applying the given schema to all
+// of the merged bodies and merging the result.
+//
+// Although required attributes _are_ supported, they should be used sparingly
+// with merged bodies since in this case there is no contextual information
+// with which to return good diagnostics. Applications working with merged
+// bodies may wish to mark all attributes as optional and then check for
+// required attributes afterwards, to produce better diagnostics.
+func (mb mergedBodies) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
+	// the returned body will always be empty in this case, because mergedContent
+	// will only ever call Content on the child bodies.
+	content, _, diags := mb.mergedContent(schema, false)
+	return content, diags
+}
+
+func (mb mergedBodies) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
+	return mb.mergedContent(schema, true)
+}
+
+func (mb mergedBodies) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
+	attrs := make(map[string]*hcl.Attribute)
+	var diags hcl.Diagnostics
+
+	for _, body := range mb {
+		thisAttrs, thisDiags := body.JustAttributes()
+
+		if len(thisDiags) != 0 {
+			diags = append(diags, thisDiags...)
+		}
+
+		if thisAttrs != nil {
+			for name, attr := range thisAttrs {
+				if existing := attrs[name]; reportDuplicates && existing != nil {
+					diags = diags.Append(&hcl.Diagnostic{
+						Severity: hcl.DiagError,
+						Summary:  "Duplicate argument",
+						Detail: fmt.Sprintf(
+							"Argument %q was already set at %s",
+							name, existing.NameRange.String(),
+						),
+						Subject: &attr.NameRange,
+					})
+					continue
+				}
+
+				attrs[name] = attr
+			}
+		}
+	}
+
+	return attrs, diags
+}
+
+func (mb mergedBodies) MissingItemRange() hcl.Range {
+	if len(mb) == 0 {
+		// Nothing useful to return here, so we'll return some garbage.
+		return hcl.Range{
+			Filename: "<empty>",
+		}
+	}
+
+	// arbitrarily use the first body's missing item range
+	return mb[0].MissingItemRange()
+}
+
+func (mb mergedBodies) mergedContent(schema *hcl.BodySchema, partial bool) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
+	// We need to produce a new schema with none of the attributes marked as
+	// required, since _any one_ of our bodies can contribute an attribute value.
+	// We'll separately check that all required attributes are present at
+	// the end.
+	mergedSchema := &hcl.BodySchema{
+		Blocks: schema.Blocks,
+	}
+	for _, attrS := range schema.Attributes {
+		mergedAttrS := attrS
+		mergedAttrS.Required = false
+		mergedSchema.Attributes = append(mergedSchema.Attributes, mergedAttrS)
+	}
+
+	var mergedLeftovers []hcl.Body
+	content := &hcl.BodyContent{
+		Attributes: map[string]*hcl.Attribute{},
+	}
+
+	var diags hcl.Diagnostics
+	for _, body := range mb {
+		var thisContent *hcl.BodyContent
+		var thisLeftovers hcl.Body
+		var thisDiags hcl.Diagnostics
+
+		if partial {
+			thisContent, thisLeftovers, thisDiags = body.PartialContent(mergedSchema)
+		} else {
+			thisContent, thisDiags = body.Content(mergedSchema)
+		}
+
+		if thisLeftovers != nil {
+			mergedLeftovers = append(mergedLeftovers, thisLeftovers)
+		}
+		if len(thisDiags) != 0 {
+			diags = append(diags, thisDiags...)
+		}
+
+		if thisContent.Attributes != nil {
+			for name, attr := range thisContent.Attributes {
+				if existing := content.Attributes[name]; reportDuplicates && existing != nil {
+					diags = diags.Append(&hcl.Diagnostic{
+						Severity: hcl.DiagError,
+						Summary:  "Duplicate argument",
+						Detail: fmt.Sprintf(
+							"Argument %q was already set at %s",
+							name, existing.NameRange.String(),
+						),
+						Subject: &attr.NameRange,
+					})
+					continue
+				}
+				content.Attributes[name] = attr
+			}
+		}
+
+		if len(thisContent.Blocks) != 0 {
+			content.Blocks = append(content.Blocks, thisContent.Blocks...)
+		}
+	}
+
+	// Finally, we check for required attributes.
+	for _, attrS := range schema.Attributes {
+		if !attrS.Required {
+			continue
+		}
+
+		if content.Attributes[attrS.Name] == nil {
+			// We don't have any context here to produce a good diagnostic,
+			// which is why we warn in the Content docstring to minimize the
+			// use of required attributes on merged bodies.
+			diags = diags.Append(&hcl.Diagnostic{
+				Severity: hcl.DiagError,
+				Summary:  "Missing required argument",
+				Detail: fmt.Sprintf(
+					"The argument %q is required, but was not set.",
+					attrS.Name,
+				),
+			})
+		}
+	}
+
+	leftoverBody := MergeBodies(mergedLeftovers)
+	return content, leftoverBody, diags
+}
+
+// JustAllAttributes returns a list of attributes in order. Since these bodies got added as partialContent
+// we do not have to supply a scheme or handle the underlying diagnostics.
+func (mb mergedBodies) JustAllAttributes() []hcl.Attributes {
+	return mb.JustAllAttributesWithName("")
+}
+
+// JustAllAttributesWithName behaviour is the same as JustAllAttributes with a filtered slice.
+func (mb mergedBodies) JustAllAttributesWithName(name string) []hcl.Attributes {
+	var result []hcl.Attributes
+
+	for _, body := range mb {
+		attrs, _ := body.JustAttributes()
+		for attrName := range attrs {
+			if name != "" && attrName != name {
+				delete(attrs, attrName)
+			}
+		}
+		if len(attrs) > 0 {
+			result = append(result, attrs)
+		}
+	}
+	return result
+}
diff --git a/config/couper_file.go b/config/couper_file.go
new file mode 100644
index 00000000..2547c9bc
--- /dev/null
+++ b/config/couper_file.go
@@ -0,0 +1,25 @@
+package config
+
+import (
+	"os"
+	"path/filepath"
+
+	"github.com/hashicorp/hcl/v2"
+)
+
+const DefaultFileName = "couper.hcl"
+
+type CouperFile struct {
+	Bytes       []byte
+	Context     *hcl.EvalContext
+	Definitions *Definitions `hcl:"definitions,block"`
+	Server      []*Server    `hcl:"server,block"`
+	Settings    *Settings    `hcl:"settings,block"`
+}
+
+func SetWorkingDirectory(configFile string) (string, error) {
+	if err := os.Chdir(filepath.Dir(configFile)); err != nil {
+		return "", err
+	}
+	return os.Getwd()
+}
diff --git a/config/definitions.go b/config/definitions.go
index 461c02b4..9f9090c7 100644
--- a/config/definitions.go
+++ b/config/definitions.go
@@ -1,7 +1,22 @@
 package config
 
+import "fmt"
+
 type Definitions struct {
 	Backend   []*Backend   `hcl:"backend,block"`
 	BasicAuth []*BasicAuth `hcl:"basic_auth,block"`
 	JWT       []*JWT       `hcl:"jwt,block"`
 }
+
+func (d *Definitions) BackendWithName(name string) (*Backend, error) {
+	if name == "" {
+		return nil, nil
+	}
+
+	for _, backend := range d.Backend {
+		if backend.Name == name {
+			return backend, nil
+		}
+	}
+	return nil, fmt.Errorf("missing backend reference: %q", name)
+}
diff --git a/config/endpoint.go b/config/endpoint.go
index b68b095f..ab6d8fce 100644
--- a/config/endpoint.go
+++ b/config/endpoint.go
@@ -16,10 +16,16 @@ type Endpoint struct {
 	Remain               hcl.Body `hcl:",remain" json:"-"`
 }
 
+type Endpoints []*Endpoint
+
 func (e Endpoint) Body() hcl.Body {
 	return e.Remain
 }
 
+func (e Endpoint) Reference() string {
+	return e.Backend
+}
+
 func (e Endpoint) Schema(inline bool) *hcl.BodySchema {
 	if !inline {
 		schema, _ := gohcl.ImpliedBodySchema(e)
diff --git a/config/env/env.go b/config/env/env.go
index 64a2f391..e984d054 100644
--- a/config/env/env.go
+++ b/config/env/env.go
@@ -45,8 +45,12 @@ func DecodeWithPrefix(conf interface{}, prefix string) {
 		}
 
 		envVal, ok := field.Tag.Lookup("env")
-		if !ok {
-			continue
+		if !ok { // fallback to hcl struct tag
+			envVal, ok = field.Tag.Lookup("hcl")
+			if !ok {
+				continue
+			}
+			envVal = strings.Split(envVal, ",")[0]
 		}
 
 		mapVal, exist := envMap[envVal]
diff --git a/config/gateway.go b/config/gateway.go
deleted file mode 100644
index f08b7bd8..00000000
--- a/config/gateway.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package config
-
-import "github.com/hashicorp/hcl/v2"
-
-type Gateway struct {
-	Bytes       []byte           `hcl:"-"`
-	Context     *hcl.EvalContext `hcl:"-"`
-	Definitions *Definitions     `hcl:"definitions,block"`
-	Server      []*Server        `hcl:"server,block"`
-	Settings    *Settings        `hcl:"settings,block"`
-}
diff --git a/config/inline.go b/config/inline.go
index 862f34d1..9b606483 100644
--- a/config/inline.go
+++ b/config/inline.go
@@ -4,5 +4,6 @@ import "github.com/hashicorp/hcl/v2"
 
 type Inline interface {
 	Body() hcl.Body
+	Reference() string
 	Schema(inline bool) *hcl.BodySchema
 }
diff --git a/config/load.go b/config/load.go
deleted file mode 100644
index 982d0bc1..00000000
--- a/config/load.go
+++ /dev/null
@@ -1,43 +0,0 @@
-package config
-
-import (
-	"fmt"
-	"io/ioutil"
-	"os"
-	"path"
-	"path/filepath"
-
-	"github.com/hashicorp/hcl/v2/hclsimple"
-
-	"github.com/avenga/couper/eval"
-)
-
-func LoadFile(filePath string) (*Gateway, error) {
-	wd, err := os.Getwd()
-	if err != nil {
-		return nil, err
-	}
-	src, err := ioutil.ReadFile(path.Join(wd, filePath))
-	if err != nil {
-		return nil, fmt.Errorf("failed to load configuration: %w", err)
-	}
-	return LoadBytes(src, filePath)
-}
-
-func LoadBytes(src []byte, filePath string) (*Gateway, error) {
-	config := &Gateway{
-		Bytes:    src[:],
-		Context:  eval.NewENVContext(src),
-		Settings: &Settings{DefaultPort: DefaultListenPort},
-	}
-	filename := filepath.Base(filePath)
-	if filepath.Ext(filename) != ".hcl" {
-		return nil, fmt.Errorf("configuration must be a hcl file")
-	}
-
-	// filename must match .hcl ending for further []byte processing
-	if err := hclsimple.Decode(filename, src, config.Context, config); err != nil {
-		return nil, fmt.Errorf("Failed to load configuration bytes: %w", err)
-	}
-	return config, nil
-}
diff --git a/config/parser/load.go b/config/parser/load.go
new file mode 100644
index 00000000..c43f72b5
--- /dev/null
+++ b/config/parser/load.go
@@ -0,0 +1,26 @@
+package parser
+
+import (
+	"strings"
+
+	"github.com/hashicorp/hcl/v2"
+	"github.com/hashicorp/hcl/v2/hclparse"
+)
+
+func Load(src []byte, name string) (hcl.Body, hcl.Diagnostics) {
+	parser := hclparse.NewParser()
+
+	var file *hcl.File
+	var diags hcl.Diagnostics
+
+	if strings.HasSuffix(name, ".json") {
+		file, diags = parser.ParseJSON(src, name)
+	} else {
+		file, diags = parser.ParseHCL(src, name)
+	}
+
+	if file == nil || file.Body == nil {
+		return hcl.EmptyBody(), diags
+	}
+	return file.Body, diags
+}
diff --git a/config/runtime/body.go b/config/runtime/body.go
deleted file mode 100644
index f7d9cda7..00000000
--- a/config/runtime/body.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package runtime
-
-import "github.com/hashicorp/hcl/v2"
-
-var _ hcl.Body = &Body{}
-
-type Body struct {
-	hcl.BodyContent
-}
-
-func NewBody(content *hcl.BodyContent) hcl.Body {
-	return &Body{*content}
-}
-
-func (e *Body) Content(_ *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
-	return &e.BodyContent, nil
-}
-
-func (e *Body) PartialContent(_ *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
-	return &e.BodyContent, e, nil
-}
-
-func (e *Body) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
-	return e.Attributes, nil
-}
-
-func (e *Body) MissingItemRange() hcl.Range {
-	return e.BodyContent.MissingItemRange
-}
diff --git a/config/runtime/config.go b/config/runtime/config.go
deleted file mode 100644
index 8e9d09b7..00000000
--- a/config/runtime/config.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package runtime
-
-import "github.com/avenga/couper/config"
-
-type Config struct {
-	File      string `env:"config_file"`
-	LogFormat string `env:"log_format"`
-}
-
-// NewConfig creates the runtime config which could be overridden in order:
-// internal.defaults -> config.settings -> flag.args -> env.vars
-func NewConfig(c *config.Gateway) *Config {
-	defaultConf := *DefaultConfig
-	conf := &defaultConf
-	if c != nil && c.Settings != nil {
-		conf.Merge(newConfigFrom(c.Settings))
-	}
-
-	return conf
-}
-
-func newConfigFrom(s *config.Settings) *Config {
-	return &Config{
-		LogFormat: s.LogFormat,
-	}
-}
-
-func (c *Config) Merge(o *Config) *Config {
-	if o == nil {
-		return c
-	}
-
-	if o.File != "" {
-		c.File = o.File
-	}
-
-	if o.LogFormat != "" {
-		c.LogFormat = o.LogFormat
-	}
-
-	return c
-}
diff --git a/config/runtime/http.go b/config/runtime/http.go
index 7c566aea..74f566e4 100644
--- a/config/runtime/http.go
+++ b/config/runtime/http.go
@@ -1,102 +1,24 @@
 package runtime
 
 import (
-	"os"
-	"path/filepath"
 	"time"
-
-	"github.com/avenga/couper/config"
 )
 
-// HTTPConfig represents the configuration of the ingress HTTP server.
-type HTTPConfig struct {
-	HealthPath      string `env:"health_path"`
-	ListenPort      int    `env:"default_port"`
-	UseXFH          bool   `env:"xfh"`
-	RequestIDFormat string `env:"request_id_format"`
-	Timings         HTTPTimings
-}
-
 type HTTPTimings struct {
-	IdleTimeout       time.Duration
-	ReadHeaderTimeout time.Duration
+	IdleTimeout       time.Duration `env:"timing_idle_timeout"`
+	ReadHeaderTimeout time.Duration `env:"timing_read_header_timeout"`
 	// ShutdownDelay determines the time between marking the http server
 	// as unhealthy and calling the final shutdown method which denies accepting new requests.
-	ShutdownDelay time.Duration
+	ShutdownDelay time.Duration `env:"timing_shutdown_delay"`
 	// ShutdownTimeout is the context duration for shutting down the http server. Running requests
 	// gets answered and those which exceeded this timeout getting lost. In combination with
 	// ShutdownDelay the load-balancer should have picked another instance already.
-	ShutdownTimeout time.Duration
-}
-
-// DefaultConfig sets some defaults for runtime.
-var DefaultConfig = &Config{
-	File:      "couper.hcl",
-	LogFormat: "common",
-}
-
-// DefaultHTTP sets some defaults for the ingress HTTP server.
-var DefaultHTTP = &HTTPConfig{
-	HealthPath: "/healthz",
-	Timings: HTTPTimings{
-		IdleTimeout:       time.Second * 60,
-		ReadHeaderTimeout: time.Second * 10,
-		ShutdownDelay:     time.Second * 5,
-		ShutdownTimeout:   time.Second * 5,
-	},
-	ListenPort:      config.DefaultListenPort,
-	RequestIDFormat: "common",
-}
-
-// NewHTTPConfig creates the server config which could be overridden in order:
-// internal.defaults -> config.settings -> flag.args -> env.vars
-func NewHTTPConfig(c *config.Gateway) *HTTPConfig {
-	defaultConf := *DefaultHTTP
-	conf := &defaultConf
-	if c != nil && c.Settings != nil {
-		conf.Merge(newHTTPConfigFrom(c.Settings))
-	}
-
-	return conf
-}
-
-func newHTTPConfigFrom(s *config.Settings) *HTTPConfig {
-	return &HTTPConfig{
-		HealthPath:      s.HealthPath,
-		ListenPort:      s.DefaultPort,
-		UseXFH:          s.XForwardedHost,
-		RequestIDFormat: s.RequestIDFormat,
-		Timings:         DefaultHTTP.Timings,
-	}
-}
-
-func (c *HTTPConfig) Merge(o *HTTPConfig) *HTTPConfig {
-	if o == nil {
-		return c
-	}
-
-	if o.HealthPath != "" {
-		c.HealthPath = o.HealthPath
-	}
-
-	if o.ListenPort != 0 {
-		c.ListenPort = o.ListenPort
-	}
-
-	if o.UseXFH != c.UseXFH {
-		c.UseXFH = o.UseXFH
-	}
-
-	if o.RequestIDFormat != "" {
-		c.RequestIDFormat = o.RequestIDFormat
-	}
-
-	return c
+	ShutdownTimeout time.Duration `env:"timing_shutdown_timeout"`
 }
 
-func SetWorkingDirectory(configFile string) (string, error) {
-	if err := os.Chdir(filepath.Dir(configFile)); err != nil {
-		return "", err
-	}
-	return os.Getwd()
+var DefaultTimings = HTTPTimings{
+	IdleTimeout:       time.Second * 60,
+	ReadHeaderTimeout: time.Second * 10,
+	ShutdownDelay:     time.Second * 5,
+	ShutdownTimeout:   time.Second * 5,
 }
diff --git a/config/runtime/server.go b/config/runtime/server.go
index 051e32a3..db1ea788 100644
--- a/config/runtime/server.go
+++ b/config/runtime/server.go
@@ -8,7 +8,6 @@ import (
 	"net/http/httptest"
 	"os"
 	"path"
-	"reflect"
 	"strconv"
 	"strings"
 
@@ -34,25 +33,13 @@ var DefaultBackendConf = &config.Backend{
 	Timeout:          "300s",
 }
 
-var (
-	errorMissingBackend = fmt.Errorf("no backend attribute reference or block")
-	errorMissingServer  = fmt.Errorf("missing server definitions")
-)
-
-type backendDefinition struct {
-	conf    *config.Backend
-	handler http.Handler
-}
-
 type Port int
 
 func (p Port) String() string {
 	return strconv.Itoa(int(p))
 }
 
-type ServerConfiguration struct {
-	PortOptions map[Port]*MuxOptions
-}
+type ServerConfiguration map[Port]*MuxOptions
 
 type hosts map[string]bool
 type ports map[Port]hosts
@@ -67,16 +54,8 @@ const (
 
 // NewServerConfiguration sets http handler specific defaults and validates the given gateway configuration.
 // Wire up all endpoints and maps them within the returned Server.
-func NewServerConfiguration(conf *config.Gateway, httpConf *HTTPConfig, log *logrus.Entry) (*ServerConfiguration, error) {
-	if len(conf.Server) == 0 {
-		return nil, errorMissingServer
-	}
-
-	// (arg && env) > conf
+func NewServerConfiguration(conf *config.CouperFile, log *logrus.Entry) (ServerConfiguration, error) {
 	defaultPort := conf.Settings.DefaultPort
-	if httpConf.ListenPort != defaultPort {
-		defaultPort = httpConf.ListenPort
-	}
 
 	// confCtx is created to evaluate request / response related configuration errors on start.
 	noopReq := httptest.NewRequest(http.MethodGet, "https://couper.io", nil)
@@ -89,21 +68,18 @@ func NewServerConfiguration(conf *config.Gateway, httpConf *HTTPConfig, log *log
 		return nil, err
 	}
 
-	backends, err := newBackendsFromDefinitions(conf, confCtx, log)
-	if err != nil {
-		return nil, err
-	}
-
 	accessControls, err := configureAccessControls(conf, confCtx)
 	if err != nil {
 		return nil, err
 	}
 
-	serverConfiguration := &ServerConfiguration{PortOptions: map[Port]*MuxOptions{
-		Port(defaultPort): NewMuxOptions(hostsMap)},
-	}
-	for p := range validPortMap {
-		serverConfiguration.PortOptions[p] = NewMuxOptions(hostsMap)
+	serverConfiguration := make(ServerConfiguration)
+	if len(validPortMap) == 0 {
+		serverConfiguration[Port(defaultPort)] = NewMuxOptions(hostsMap)
+	} else {
+		for p := range validPortMap {
+			serverConfiguration[p] = NewMuxOptions(hostsMap)
+		}
 	}
 
 	api := make(map[*config.Endpoint]http.Handler)
@@ -153,7 +129,7 @@ func NewServerConfiguration(conf *config.Gateway, httpConf *HTTPConfig, log *log
 			// map backends to endpoint
 			endpoints := make(map[string]bool)
 
-			for _, endpoint := range srvConf.API.Endpoint {
+			for _, endpoint := range srvConf.API.Endpoints {
 				pattern := utils.JoinPath("/", serverOptions.APIBasePath, endpoint.Pattern)
 
 				unique, cleanPattern := isUnique(endpoints, pattern)
@@ -162,10 +138,6 @@ func NewServerConfiguration(conf *config.Gateway, httpConf *HTTPConfig, log *log
 				}
 				endpoints[cleanPattern] = true
 
-				if err := validateInlineScheme(confCtx, endpoint.Remain, endpoint); err != nil {
-					return nil, err
-				}
-
 				// setACHandlerFn individual wrap for access_control configuration per endpoint
 				setACHandlerFn := func(protectedHandler http.Handler) {
 					api[endpoint] = configureProtectedHandler(accessControls, serverOptions.APIErrTpl,
@@ -175,29 +147,18 @@ func NewServerConfiguration(conf *config.Gateway, httpConf *HTTPConfig, log *log
 						protectedHandler)
 				}
 
-				// lookup for backend reference, prefer endpoint definition over api one
-				var backend http.Handler
-				if endpoint.Backend != "" {
-					if _, ok := backends[endpoint.Backend]; !ok {
-						return nil, fmt.Errorf("backend %q is not defined", endpoint.Backend)
-					}
-					// set server context for defined backends
-					beConf := backends[endpoint.Backend]
-					_, remain := beConf.Merge(&config.Backend{Remain: endpoint.Remain})
-					backend, err = newProxy(confCtx, beConf, srvConf.API.CORS, remain, log, serverOptions)
-					if err != nil {
-						return nil, err
-					}
-				} else {
-					// otherwise try to parse an inline block and fallback for api reference or inline block
-					inlineBackend, err := newInlineBackend(confCtx, conf.Bytes, backends, srvConf.API, endpoint, log, serverOptions)
-					if err != nil { // TODO hcl.diagnostics error
-						return nil, fmt.Errorf("range: %s: %v", endpoint.Remain.MissingItemRange().String(), err)
-					}
-					backend = inlineBackend
+				backendConf := *DefaultBackendConf
+				if diags := gohcl.DecodeBody(endpoint.Remain, confCtx, &backendConf); diags.HasErrors() {
+					return nil, diags
+				}
+
+				backend, err := newProxy(confCtx, &backendConf, srvConf.API.CORS, log, serverOptions)
+				if err != nil {
+					return nil, err
 				}
 
 				setACHandlerFn(backend)
+
 				err = setRoutesFromHosts(serverConfiguration, defaultPort, srvConf.Hosts, pattern, api[endpoint], KindAPI)
 				if err != nil {
 					return nil, err
@@ -208,25 +169,17 @@ func NewServerConfiguration(conf *config.Gateway, httpConf *HTTPConfig, log *log
 	return serverConfiguration, nil
 }
 
-func newProxy(ctx *hcl.EvalContext, beConf *config.Backend, corsOpts *config.CORS, remainCtx []hcl.Body, log *logrus.Entry, srvOpts *server.Options) (http.Handler, error) {
-	corsOptions, err := handler.NewCORSOptions(corsOpts)
-	if err != nil {
+func newProxy(ctx *hcl.EvalContext, beConf *config.Backend, corsOpts *config.CORS, log *logrus.Entry, srvOpts *server.Options) (http.Handler, error) {
+	if err := validateOrigin(ctx, beConf); err != nil {
 		return nil, err
 	}
 
-	for _, name := range []string{"request_headers", "response_headers"} {
-		for _, body := range remainCtx {
-			content, _, err := body.PartialContent(config.Backend{}.Schema(true))
-			if err != nil {
-				return nil, err
-			}
-			if _, ok := content.Attributes[name]; ok {
-				log.Warningf("'%s' is deprecated, use 'set_%s' instead", name, name)
-			}
-		}
+	corsOptions, err := handler.NewCORSOptions(corsOpts)
+	if err != nil {
+		return nil, err
 	}
 
-	proxyOptions, err := handler.NewProxyOptions(beConf, corsOptions, remainCtx)
+	proxyOptions, err := handler.NewProxyOptions(beConf, corsOptions)
 	if err != nil {
 		return nil, err
 	}
@@ -234,47 +187,6 @@ func newProxy(ctx *hcl.EvalContext, beConf *config.Backend, corsOpts *config.COR
 	return handler.NewProxy(proxyOptions, log, srvOpts, ctx)
 }
 
-func newBackendsFromDefinitions(conf *config.Gateway, confCtx *hcl.EvalContext, log *logrus.Entry) (map[string]*config.Backend, error) {
-	backends := make(map[string]*config.Backend)
-
-	if conf.Definitions == nil {
-		return backends, nil
-	}
-
-	for _, beConf := range conf.Definitions.Backend {
-		if _, ok := backends[beConf.Name]; ok {
-			return nil, fmt.Errorf("backend name must be unique: %q", beConf.Name)
-		}
-
-		origin := getAttribute(confCtx, "origin", beConf.Remain, conf.Bytes)
-		if e := validateOrigin(origin, beConf.Remain.MissingItemRange()); e != nil {
-			return nil, e
-		}
-
-		beConf, _ = DefaultBackendConf.Merge(beConf)
-		backends[beConf.Name] = beConf
-	}
-	return backends, nil
-}
-
-// hasAttribute checks for a configured string value and ignores unrelated errors.
-func getAttribute(ctx *hcl.EvalContext, name string, body hcl.Body, configBytes []byte) string {
-	attr, _ := body.JustAttributes()
-
-	if _, ok := attr[name]; !ok {
-		return ""
-	}
-
-	val, diags := attr[name].Expr.Value(ctx)
-	if diags.HasErrors() && attr[name].Expr.Range().CanSliceBytes(configBytes) { // fallback to origin string
-		rawString := attr[name].Expr.Range().SliceBytes(configBytes)
-		if len(rawString) > 2 { // more then quotes
-			return string(attr[name].Expr.Range().SliceBytes(configBytes)[1 : len(rawString)-1]) //unquote
-		}
-	}
-	return seetie.ValueToString(val)
-}
-
 func splitWildcardHostPort(host string, configuredPort int) (string, Port, error) {
 	if !strings.Contains(host, ":") {
 		return host, Port(configuredPort), nil
@@ -300,7 +212,7 @@ func splitWildcardHostPort(host string, configuredPort int) (string, Port, error
 	return ho, Port(po), nil
 }
 
-func configureAccessControls(conf *config.Gateway, confCtx *hcl.EvalContext) (ac.Map, error) {
+func configureAccessControls(conf *config.CouperFile, confCtx *hcl.EvalContext) (ac.Map, error) {
 	accessControls := make(ac.Map)
 
 	if conf.Definitions != nil {
@@ -381,155 +293,7 @@ func configureProtectedHandler(m ac.Map, errTpl *errors.Template, parentAC, hand
 	return h
 }
 
-// newInlineBackend reads partial content of block type 'backend' which could be defined within the
-// 'api' and 'endpoint' block as inline or reference one. The result gets merged with our internal defaults
-// for timing etc. first and then with a possible reference via label, if any. Backend lookups from 'definitions'
-// must consider parents path overrides.
-func newInlineBackend(
-	evalCtx *hcl.EvalContext, confBytes []byte, backends map[string]*config.Backend,
-	parentAPI *config.Api, inlineDef config.Inline, log *logrus.Entry, srvOpts *server.Options,
-) (http.Handler, error) {
-	var parentBackend *config.Backend
-	var bodies []hcl.Body
-	if parentAPI.Backend != "" {
-		be, ok := backends[parentAPI.Backend]
-		if !ok {
-			return nil, fmt.Errorf("referenced backend does not exist: %q", parentAPI.Backend)
-		}
-		parentBackend = be
-		bodies = append(bodies, be.Remain)
-	} else {
-		inlineBlock, err := getBackendInlineBlock(parentAPI, evalCtx)
-		if err != nil && err != errorMissingBackend {
-			return nil, err
-		} else if err == nil {
-			parentBackend = &config.Backend{}
-			diags := gohcl.DecodeBody(inlineBlock.Body, evalCtx, parentBackend)
-			if diags.HasErrors() {
-				return nil, diags
-			}
-			bodies = append(bodies, parentBackend.Remain)
-		}
-	}
-
-	var backendConf *config.Backend
-	inlineBlock, err := getBackendInlineBlock(inlineDef, evalCtx)
-	if err != nil {
-		if err == errorMissingBackend && parentBackend != nil {
-			backendConf = parentBackend
-
-		} else {
-			return nil, err
-		}
-	} else {
-		backendConf = &config.Backend{}
-		diags := gohcl.DecodeBody(inlineBlock.Body, evalCtx, backendConf)
-		if diags.HasErrors() {
-			return nil, diags
-		}
-
-		parentAttr, _ := inlineDef.Body().JustAttributes()
-		content := &hcl.BodyContent{
-			Attributes:       parentAttr,
-			MissingItemRange: inlineDef.Body().MissingItemRange(),
-		}
-		bodies = append(bodies, NewBody(content))
-		bodies = append(bodies, backendConf.Body())
-	}
-
-	backendConf, _ = DefaultBackendConf.Merge(backendConf)
-
-	// obtain the backend reference and merge with the current override
-	if inlineBlock != nil && len(inlineBlock.Labels) > 0 {
-		backendConf.Name = inlineBlock.Labels[0]
-		if beRef, ok := backends[backendConf.Name]; ok {
-			// consider existing parents, rebuild hierarchy
-			mergedBackendConf, _ := beRef.Merge(backendConf)
-			bodies = append([]hcl.Body{beRef.Remain}, bodies...)
-			backendConf = mergedBackendConf
-		} else {
-			return nil, fmt.Errorf("override backend %q is not defined", backendConf.Name)
-		}
-	}
-
-	// since we reference a backend we must append the current context inline definition
-	// to handle possible overrides like 'path' for endpoints. Only if the most recent definition
-	// has no own attribute defined, use the parents one.
-	if len(bodies) > 0 && reflect.TypeOf(inlineDef) == reflect.TypeOf(&config.Endpoint{}) {
-		inheritableAttributes := []string{"path"}
-		inheritAttributes := make(hcl.Attributes)
-
-		recentBody := bodies[len(bodies)-1]
-		attr, _ := recentBody.JustAttributes()
-
-		for _, inheritableAttr := range inheritableAttributes {
-			var recentAttrStr string
-			recentAttr, ok := attr[inheritableAttr]
-			if ok {
-				attrVal, _ := recentAttr.Expr.Value(evalCtx)
-				recentAttrStr = seetie.ValueToString(attrVal)
-			}
-			if recentAttrStr == "" || (parentBackend != nil && recentBody == parentBackend.Body()) {
-				// and if the endpoint has defined a attribute
-				attr, _ = inlineDef.Body().JustAttributes()
-				if recentAttr, ok = attr[inheritableAttr]; ok {
-					inheritAttributes[inheritableAttr] = recentAttr
-				}
-			}
-		}
-
-		if len(inheritAttributes) > 0 {
-			content := &hcl.BodyContent{
-				Attributes:       inheritAttributes,
-				MissingItemRange: inlineDef.Body().MissingItemRange(),
-			}
-			bodies = append(bodies, NewBody(content))
-		}
-	}
-
-	var origin string
-	for i := len(bodies); i > 0; i-- {
-		if o := getAttribute(evalCtx, "origin", bodies[i-1], confBytes); o != "" {
-			origin = o
-			break
-		}
-	}
-
-	if err = validateOrigin(origin, backendConf.Remain.MissingItemRange()); err != nil {
-		return nil, err
-	}
-
-	return newProxy(evalCtx, backendConf, parentAPI.CORS, bodies, log, srvOpts)
-}
-
-func getBackendInlineBlock(inline config.Inline, evalCtx *hcl.EvalContext) (*hcl.Block, error) {
-	content, _, diags := inline.Body().PartialContent(inline.Schema(true))
-	if diags.HasErrors() {
-		return nil, diags
-	}
-
-	if content == nil || len(content.Blocks) == 0 {
-		return nil, errorMissingBackend
-	}
-
-	var inlineBlock *hcl.Block
-	for _, block := range content.Blocks {
-		if block.Type == "backend" {
-			if err := validateInlineScheme(evalCtx, block.Body, config.Backend{}); err != nil {
-				return nil, err
-			}
-			inlineBlock = block
-			break
-		}
-	}
-
-	if inlineBlock == nil {
-		return nil, errorMissingBackend
-	}
-	return inlineBlock, nil
-}
-
-func setRoutesFromHosts(srvConf *ServerConfiguration, confPort int, hosts []string, path string, handler http.Handler, kind HandlerKind) error {
+func setRoutesFromHosts(srvConf ServerConfiguration, defaultPort int, hosts []string, path string, handler http.Handler, kind HandlerKind) error {
 	hostList := hosts
 	if len(hostList) == 0 {
 		hostList = []string{"*"}
@@ -537,7 +301,7 @@ func setRoutesFromHosts(srvConf *ServerConfiguration, confPort int, hosts []stri
 
 	for _, h := range hostList {
 		joinedPath := utils.JoinPath("/", path)
-		host, listenPort, err := splitWildcardHostPort(h, confPort)
+		host, listenPort, err := splitWildcardHostPort(h, defaultPort)
 		if err != nil {
 			return err
 		}
@@ -552,11 +316,11 @@ func setRoutesFromHosts(srvConf *ServerConfiguration, confPort int, hosts []stri
 
 		switch kind {
 		case KindAPI:
-			routes = srvConf.PortOptions[listenPort].EndpointRoutes
+			routes = srvConf[listenPort].EndpointRoutes
 		case KindFiles:
-			routes = srvConf.PortOptions[listenPort].FileRoutes
+			routes = srvConf[listenPort].FileRoutes
 		case KindSPA:
-			routes = srvConf.PortOptions[listenPort].SPARoutes
+			routes = srvConf[listenPort].SPARoutes
 		default:
 			return fmt.Errorf("unknown route kind")
 		}
diff --git a/config/runtime/server/options.go b/config/runtime/server/options.go
index 3e88c9a3..697472c5 100644
--- a/config/runtime/server/options.go
+++ b/config/runtime/server/options.go
@@ -27,9 +27,13 @@ func NewServerOptions(conf *config.Server) (*Options, error) {
 		APIErrTpl:    errors.DefaultJSON,
 		FileErrTpl:   errors.DefaultHTML,
 		ServerErrTpl: errors.DefaultHTML,
-		ServerName:   conf.Name,
 	}
 
+	if conf == nil {
+		return options, nil
+	}
+	options.ServerName = conf.Name
+
 	if conf.ErrorFile != "" {
 		tpl, err := errors.NewTemplateFromFile(conf.ErrorFile)
 		if err != nil {
diff --git a/config/runtime/server_validation.go b/config/runtime/server_validation.go
index 4c54d841..c70794dc 100644
--- a/config/runtime/server_validation.go
+++ b/config/runtime/server_validation.go
@@ -3,15 +3,14 @@ package runtime
 import (
 	"fmt"
 	"net/url"
-	"reflect"
 	"regexp"
 	"strings"
 
-	"github.com/hashicorp/hcl/v2"
-	"github.com/hashicorp/hcl/v2/gohcl"
+	"github.com/avenga/couper/internal/seetie"
 
 	ac "github.com/avenga/couper/accesscontrol"
 	"github.com/avenga/couper/config"
+	"github.com/hashicorp/hcl/v2"
 )
 
 var (
@@ -28,7 +27,7 @@ var (
 //	"*"							equals to "*:configuredPort"
 //	"host:*"					equals to "host:configuredPort"
 //	"host"						listen on configured default port for given host
-func validatePortHosts(conf *config.Gateway, configuredPort int) (ports, hosts, error) {
+func validatePortHosts(conf *config.CouperFile, configuredPort int) (ports, hosts, error) {
 	portMap := make(ports)
 	hostMap := make(hosts)
 	isHostsMandatory := len(conf.Server) > 1
@@ -78,7 +77,30 @@ func validatePortHosts(conf *config.Gateway, configuredPort int) (ports, hosts,
 	return portMap, hostMap, nil
 }
 
-func validateOrigin(origin string, ctxRange hcl.Range) error {
+func validateOrigin(ctx *hcl.EvalContext, inline config.Inline) error {
+	content, _, diags := inline.Body().PartialContent(inline.Schema(true))
+	if seetie.SetSeverityLevel(diags).HasErrors() {
+		return diags
+	}
+
+	ctxRange := inline.Body().MissingItemRange()
+
+	originAttr, ok := content.Attributes["origin"]
+	if !ok {
+		return hcl.Diagnostics{&hcl.Diagnostic{
+			Subject: &ctxRange,
+			Summary: "missing backend.origin attribute",
+		}}
+	}
+	return nil // TODO: read byte range
+
+	originValue, diags := originAttr.Expr.Value(ctx)
+	if seetie.SetSeverityLevel(diags).HasErrors() {
+		return diags
+	}
+
+	origin := seetie.ValueToString(originValue)
+
 	diagErr := &hcl.Diagnostic{
 		Subject: &ctxRange,
 		Summary: "invalid backend.origin value",
@@ -118,16 +140,6 @@ func validateACName(accessControls ac.Map, name, acType string) (string, error)
 	return name, nil
 }
 
-func validateInlineScheme(ctx *hcl.EvalContext, body hcl.Body, inlineType config.Inline) error {
-	_, partialBody, diags := body.PartialContent(inlineType.Schema(true))
-	result := reflect.New(reflect.TypeOf(inlineType))
-	diags = append(diags, gohcl.DecodeBody(partialBody, ctx, &result)...)
-	if diags.HasErrors() {
-		return diags
-	}
-	return nil
-}
-
 func isUnique(endpoints map[string]bool, pattern string) (bool, string) {
 	pattern = reCleanPattern.ReplaceAllString(pattern, "{}")
 
diff --git a/config/settings.go b/config/settings.go
index f64a89d4..90ef3565 100644
--- a/config/settings.go
+++ b/config/settings.go
@@ -1,11 +1,17 @@
 package config
 
-const DefaultListenPort = 8080
+var DefaultSettings = Settings{
+	DefaultPort:     8080,
+	HealthPath:      "/healthz",
+	LogFormat:       "common",
+	RequestIDFormat: "common",
+	XForwardedHost:  false,
+}
 
 type Settings struct {
 	DefaultPort     int    `hcl:"default_port,optional"`
 	HealthPath      string `hcl:"health_path,optional"`
 	LogFormat       string `hcl:"log_format,optional"`
-	XForwardedHost  bool   `hcl:"xfh,optional"`
 	RequestIDFormat string `hcl:"request_id_format,optional"`
+	XForwardedHost  bool   `hcl:"xfh,optional"`
 }
diff --git a/eval/buffer.go b/eval/buffer.go
index c59506cc..d3c2ebab 100644
--- a/eval/buffer.go
+++ b/eval/buffer.go
@@ -31,38 +31,37 @@ func (i BufferOption) GoString() string {
 }
 
 // MustBuffer determines if any of the hcl.bodies makes use of 'post' or 'json_body'.
-func MustBuffer(ctxBodies []hcl.Body) BufferOption {
+func MustBuffer(body hcl.Body) BufferOption {
 	result := BufferNone
-	for _, body := range ctxBodies {
-		attrs, err := body.JustAttributes()
-		if err != nil {
-			return result
-		}
-		for _, attr := range attrs {
-			for _, traversal := range attr.Expr.Variables() {
-				if len(traversal) < 2 {
-					continue
-				}
 
-				rootName := traversal.RootName()
-				if rootName != ClientRequest && rootName != BackendResponse {
-					continue
-				}
+	attrs, err := body.JustAttributes()
+	if err != nil {
+		return result
+	}
+	for _, attr := range attrs {
+		for _, traversal := range attr.Expr.Variables() {
+			if len(traversal) < 2 {
+				continue
+			}
+
+			rootName := traversal.RootName()
+			if rootName != ClientRequest && rootName != BackendResponse {
+				continue
+			}
 
-				nameField := reflect.ValueOf(traversal[1]).FieldByName("Name")
-				name := nameField.String()
-				switch name {
-				case JsonBody:
-					switch rootName {
-					case ClientRequest:
-						result |= BufferRequest
-					case BackendResponse:
-						result |= BufferResponse
-					}
-				case Post:
-					if rootName == ClientRequest {
-						result |= BufferRequest
-					}
+			nameField := reflect.ValueOf(traversal[1]).FieldByName("Name")
+			name := nameField.String()
+			switch name {
+			case JsonBody:
+				switch rootName {
+				case ClientRequest:
+					result |= BufferRequest
+				case BackendResponse:
+					result |= BufferResponse
+				}
+			case Post:
+				if rootName == ClientRequest {
+					result |= BufferRequest
 				}
 			}
 		}
diff --git a/go.mod b/go.mod
index 4c5ec8b5..be0e64aa 100644
--- a/go.mod
+++ b/go.mod
@@ -13,5 +13,5 @@ require (
 	github.com/zclconf/go-cty v1.2.0
 	golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
 	golang.org/x/net v0.0.0-20201021035429-f5854403a974
-	golang.org/x/tools v0.0.0-20201206230334-368bee879bfd // indirect
+	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
 )
diff --git a/go.sum b/go.sum
index 4edc452c..169168e3 100644
--- a/go.sum
+++ b/go.sum
@@ -12,8 +12,6 @@ github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbY
 github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
 github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
 github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
-github.com/getkin/kin-openapi v0.23.0 h1:RKtVNKk8kxcTIWEswgZ3Olvn1RxWOJ0zz8cP3d9aHIA=
-github.com/getkin/kin-openapi v0.23.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw=
 github.com/getkin/kin-openapi v0.33.0 h1:KccukV3/1h95R0OP7vfWB3KVy9lxA5i8i3BwlA3tRpE=
 github.com/getkin/kin-openapi v0.33.0/go.mod h1:ZJSfy1PxJv2QQvH9EdBj3nupRTVvV42mkW6zKUlRBwk=
 github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
@@ -27,11 +25,6 @@ github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3a
 github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
-github.com/hashicorp/hcl/v2 v2.5.1 h1:5ytFZykUu2/4U59ogd2f+XZdi9+6oC/Tv5WzsH6fIDA=
-github.com/hashicorp/hcl/v2 v2.5.1/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY=
-github.com/hashicorp/hcl/v2 v2.6.0 h1:3krZOfGY6SziUXa6H9PJU6TyohHn7I+ARYnhbeNBz+o=
-github.com/hashicorp/hcl/v2 v2.6.0/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY=
 github.com/hashicorp/hcl/v2 v2.7.0 h1:IU8qz5UzZ1po3M1D9/Kq6S5zbDGVfI9bnzmC1ogKKmI=
 github.com/hashicorp/hcl/v2 v2.7.0/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY=
 github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
@@ -63,37 +56,27 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
 github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
-github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/zclconf/go-cty v1.2.0 h1:sPHsy7ADcIZQP3vILvTjrh74ZA175TFP5vqiNK1UmlI=
 github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
-golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
-golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82 h1:vsphBvatvfbhlb4PO1BYSr9dzugGxJ/SQHoNufZJq1w=
 golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
-golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -103,17 +86,13 @@ golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20201206230334-368bee879bfd h1:EqFvKLTxjH6gEy2baWxX2AgJwZkBIDIcZFYcoYlI9RA=
-golang.org/x/tools v0.0.0-20201206230334-368bee879bfd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
diff --git a/handler/context_options.go b/handler/context_options.go
index d146cffa..b5ab112b 100644
--- a/handler/context_options.go
+++ b/handler/context_options.go
@@ -18,29 +18,6 @@ const (
 
 type OptionsMap map[string][]string
 
-func NewCtxOptions(attrName string, evalCtx *hcl.EvalContext, body hcl.Body) (OptionsMap, error) {
-	var diags hcl.Diagnostics
-	var options OptionsMap
-
-	content, _, d := body.PartialContent(backendInlineSchema)
-	diags = append(diags, seetie.SetSeverityLevel(d)...)
-
-	for _, attr := range content.Attributes {
-		if attr.Name != attrName {
-			continue
-		}
-		o, d := NewOptionsMap(evalCtx, attr)
-		diags = append(diags, seetie.SetSeverityLevel(d)...)
-		options = o
-		break
-	}
-
-	if diags.HasErrors() {
-		return nil, diags
-	}
-	return options, nil
-}
-
 func NewOptionsMap(evalCtx *hcl.EvalContext, attr *hcl.Attribute) (OptionsMap, hcl.Diagnostics) {
 	options := make(OptionsMap)
 	expMap, diags := seetie.ExpToMap(evalCtx, attr.Expr)
diff --git a/handler/openapi_validator.go b/handler/openapi_validator.go
index 064d6136..c4a1c089 100644
--- a/handler/openapi_validator.go
+++ b/handler/openapi_validator.go
@@ -25,20 +25,25 @@ type OpenAPIValidatorOptions struct {
 	router                   *openapi3filter.Router
 }
 
-func NewOpenAPIValidatorOptions(openapi *config.OpenAPI) (*OpenAPIValidatorOptions, error) {
-	if openapi == nil {
+// NewOpenAPIValidatorOptions takes a list of openAPI configuration due to merging configurations.
+// The last item will be set and no attributes gets merged.
+func NewOpenAPIValidatorOptions(openapi []*config.OpenAPI) (*OpenAPIValidatorOptions, error) {
+	if len(openapi) == 0 {
 		return nil, nil
 	}
+
+	openapiBlock := openapi[len(openapi)-1]
+
 	dir, err := os.Getwd()
 	if err != nil {
 		return nil, err
 	}
 
-	b, err := ioutil.ReadFile(filepath.Join(dir, openapi.File))
+	b, err := ioutil.ReadFile(filepath.Join(dir, openapiBlock.File))
 	if err != nil {
 		return nil, err
 	}
-	return NewOpenAPIValidatorOptionsFromBytes(openapi, b)
+	return NewOpenAPIValidatorOptionsFromBytes(openapiBlock, b)
 }
 
 func NewOpenAPIValidatorOptionsFromBytes(openapi *config.OpenAPI, bytes []byte) (*OpenAPIValidatorOptions, error) {
diff --git a/handler/proxy.go b/handler/proxy.go
index d49b913c..f1dd9235 100644
--- a/handler/proxy.go
+++ b/handler/proxy.go
@@ -22,6 +22,7 @@ import (
 	"golang.org/x/net/http/httpguts"
 
 	"github.com/avenga/couper/config"
+	"github.com/avenga/couper/config/body"
 	"github.com/avenga/couper/config/env"
 	"github.com/avenga/couper/config/request"
 	"github.com/avenga/couper/config/runtime/server"
@@ -60,6 +61,7 @@ type Proxy struct {
 	evalContext  *hcl.EvalContext
 	log          *logrus.Entry
 	options      *ProxyOptions
+	optionsHash  string
 	srvOptions   *server.Options
 	transport    *http.Transport
 	upstreamLog  *logging.AccessLog
@@ -127,6 +129,7 @@ func NewProxy(options *ProxyOptions, log *logrus.Entry, srvOpts *server.Options,
 		evalContext:  evalCtx,
 		log:          log,
 		options:      options,
+		optionsHash:  options.Hash(),
 		srvOptions:   srvOpts,
 		upstreamLog:  logging.NewAccessLog(&logConf, log.Logger),
 	}
@@ -135,7 +138,7 @@ func NewProxy(options *ProxyOptions, log *logrus.Entry, srvOpts *server.Options,
 }
 
 func (p *Proxy) getTransport(scheme, origin, hostname string) *http.Transport {
-	key := scheme + "|" + origin + "|" + hostname
+	key := scheme + "|" + origin + "|" + hostname + "|" + p.optionsHash
 	transport, ok := transports.Load(key)
 	if !ok {
 		var tlsConf *tls.Config
@@ -342,17 +345,16 @@ func (p *Proxy) Director(req *http.Request) error {
 
 	var origin, hostname, path string
 	evalContext := eval.NewHTTPContext(p.evalContext, p.bufferOption, req, nil, nil)
-	for _, hclContext := range p.options.Context { // context gets configured in order, last wins
-		content, _, _ := hclContext.PartialContent(backendInlineSchema)
-		if o := getAttribute(evalContext, "origin", content); o != "" {
-			origin = o
-		}
-		if h := getAttribute(evalContext, "hostname", content); h != "" {
-			hostname = h
-		}
-		if p := getAttribute(evalContext, "path", content); p != "" {
-			path = p
-		}
+
+	content, _, _ := p.options.Context.PartialContent(backendInlineSchema)
+	if o := getAttribute(evalContext, "origin", content); o != "" {
+		origin = o
+	}
+	if h := getAttribute(evalContext, "hostname", content); h != "" {
+		hostname = h
+	}
+	if pathVal := getAttribute(evalContext, "path", content); pathVal != "" {
+		path = pathVal
 	}
 
 	originURL, err := url.Parse(origin)
@@ -391,36 +393,11 @@ func (p *Proxy) Director(req *http.Request) error {
 	return nil
 }
 
-func modifyQuery(url *url.URL, del []string, set, add url.Values) {
-	query := url.Query()
-
-	for _, del := range del {
-		query.Del(del)
-	}
-	for k, values := range set {
-		query.Del(k)
-
-		for _, v := range values {
-			query.Add(k, v)
-		}
-	}
-	for k, values := range add {
-		for _, v := range values {
-			query.Add(k, v)
-		}
-	}
-
-	url.RawQuery = query.Encode()
-}
-
 func (p *Proxy) SetRoundtripContext(req *http.Request, beresp *http.Response) {
 	var (
 		attrCtx   = []string{attrReqHeaders, attrSetReqHeaders}
 		bereq     *http.Request
 		headerCtx http.Header
-
-		delQuery           []string
-		addQuery, setQuery url.Values
 	)
 
 	if beresp != nil {
@@ -429,8 +406,6 @@ func (p *Proxy) SetRoundtripContext(req *http.Request, beresp *http.Response) {
 		headerCtx = beresp.Header
 	} else if req != nil {
 		headerCtx = req.Header
-		addQuery = make(url.Values)
-		setQuery = make(url.Values)
 	}
 
 	evalCtx := eval.NewHTTPContext(p.evalContext, p.bufferOption, req, bereq, beresp)
@@ -442,41 +417,73 @@ func (p *Proxy) SetRoundtripContext(req *http.Request, beresp *http.Response) {
 		}
 	}
 
-	for _, ctxBody := range p.options.Context {
-		for _, ctx := range attrCtx { // headers
-			options, err := NewCtxOptions(ctx, evalCtx, ctxBody)
-			if err != nil {
-				p.log.WithField("parse config", p.String()).Error(err)
-			}
-			setHeaderFields(headerCtx, options)
-		}
-
-		if beresp != nil { // Do not proceed (outreq) query params if we have the answer already.
-			continue
-		}
+	allAttributes, attrOk := p.options.Context.(body.Attributes)
 
-		// query params
-		content, _, d := ctxBody.PartialContent(backendInlineSchema)
-		if diags := seetie.SetSeverityLevel(d); diags.HasErrors() {
-			p.log.WithField("parse config", p.String()).Error(diags)
-			continue
+	// apply header values
+	for _, ctxName := range attrCtx { // headers
+		if !attrOk {
+			break
 		}
 
-		if del, ok := content.Attributes[attrDelQueryParams]; ok {
-			originValue, d := del.Expr.Value(evalCtx)
-			if diags := seetie.SetSeverityLevel(d); diags.HasErrors() {
+		for _, attrs := range allAttributes.JustAllAttributesWithName(ctxName) {
+			attr, ok := attrs[ctxName]
+			if !ok {
+				continue
+			}
+			options, diags := NewOptionsMap(evalCtx, attr)
+			if diags.HasErrors() {
 				p.log.WithField("parse config", p.String()).Error(diags)
 				continue
 			}
-			delQuery = append(delQuery, seetie.ValueToStringSlice(originValue)...)
+			setHeaderFields(headerCtx, options)
 		}
-
-		p.collectQueryParams(attrAddQueryParams, evalCtx, ctxBody, addQuery)
-		p.collectQueryParams(attrSetQueryParams, evalCtx, ctxBody, setQuery)
 	}
 
-	if req != nil && beresp == nil { // just one way -> origin
-		modifyQuery(req.URL, delQuery, setQuery, addQuery)
+	// apply query params in hierarchical and logical order: delete, set, add
+	if attrOk && req != nil && beresp == nil { // just one way -> origin
+		values := req.URL.Query()
+
+		// not by name to ensure the order for all params
+		for _, attrs := range allAttributes.JustAllAttributes() {
+			attr, ok := attrs[attrDelQueryParams]
+			if ok {
+				val, diags := attr.Expr.Value(evalCtx)
+				if seetie.SetSeverityLevel(diags).HasErrors() {
+					p.log.WithField("parse config", p.String()).Error(diags)
+				}
+				for _, key := range seetie.ValueToStringSlice(val) {
+					values.Del(key)
+				}
+			}
+
+			attr, ok = attrs[attrSetQueryParams]
+			if ok {
+				options, diags := NewOptionsMap(evalCtx, attr)
+				if diags != nil {
+					p.log.WithField("parse config", p.String()).Error(diags)
+				}
+				for k, v := range options {
+					values[k] = v
+				}
+			}
+
+			attr, ok = attrs[attrAddQueryParams]
+			if ok {
+				options, diags := NewOptionsMap(evalCtx, attr)
+				if diags != nil {
+					p.log.WithField("parse config", p.String()).Error(diags)
+				}
+				for k, v := range options {
+					if _, ok = values[k]; !ok {
+						values[k] = v
+					} else {
+						values[k] = append(values[k], v...)
+					}
+				}
+			}
+		}
+
+		req.URL.RawQuery = values.Encode()
 	}
 
 	if beresp != nil && isCorsRequest(req) {
@@ -674,16 +681,6 @@ func (p *Proxy) handleUpgradeResponse(rw http.ResponseWriter, req *http.Request,
 	return
 }
 
-func (p *Proxy) collectQueryParams(attrName string, evalCtx *hcl.EvalContext, ctxBody hcl.Body, dest url.Values) {
-	options, err := NewCtxOptions(attrName, evalCtx, ctxBody)
-	if err != nil {
-		p.log.WithField("parse config", p.String()).Error(err)
-	}
-	for k, v := range options {
-		dest[k] = append(dest[k], v...)
-	}
-}
-
 func (p *Proxy) Options() *server.Options {
 	return p.srvOptions
 }
diff --git a/handler/proxy_options.go b/handler/proxy_options.go
index 6b9d646a..9f85ec73 100644
--- a/handler/proxy_options.go
+++ b/handler/proxy_options.go
@@ -1,6 +1,7 @@
 package handler
 
 import (
+	"crypto/sha256"
 	"fmt"
 	"time"
 
@@ -12,25 +13,23 @@ import (
 
 type ProxyOptions struct {
 	ConnectTimeout, Timeout, TTFBTimeout time.Duration
-	Context                              []hcl.Body
+	Context                              hcl.Body
 	BackendName                          string
 	CORS                                 *CORSOptions
 	OpenAPI                              *OpenAPIValidatorOptions
 	RequestBodyLimit                     int64
 }
 
-func NewProxyOptions(conf *config.Backend, corsOpts *CORSOptions, remainCtx []hcl.Body) (*ProxyOptions, error) {
-	totalD, err := time.ParseDuration(conf.Timeout)
-	if err != nil {
-		panic(err)
+func NewProxyOptions(conf *config.Backend, corsOpts *CORSOptions) (*ProxyOptions, error) {
+	var timeout, connTimeout, ttfbTimeout time.Duration
+	if err := parseDuration(conf.Timeout, &timeout); err != nil {
+		return nil, err
 	}
-	ttfbD, err := time.ParseDuration(conf.TTFBTimeout)
-	if err != nil {
-		panic(err)
+	if err := parseDuration(conf.TTFBTimeout, &ttfbTimeout); err != nil {
+		return nil, err
 	}
-	connectD, err := time.ParseDuration(conf.ConnectTimeout)
-	if err != nil {
-		panic(err)
+	if err := parseDuration(conf.ConnectTimeout, &connTimeout); err != nil {
+		return nil, err
 	}
 
 	bodyLimit, err := units.FromHumanSize(conf.RequestBodyLimit)
@@ -51,39 +50,27 @@ func NewProxyOptions(conf *config.Backend, corsOpts *CORSOptions, remainCtx []hc
 	return &ProxyOptions{
 		BackendName:      conf.Name,
 		CORS:             cors,
-		ConnectTimeout:   connectD,
-		Context:          remainCtx,
+		Context:          conf.Remain,
+		ConnectTimeout:   connTimeout,
 		OpenAPI:          openAPIValidatorOptions,
 		RequestBodyLimit: bodyLimit,
-		TTFBTimeout:      ttfbD,
-		Timeout:          totalD,
+		TTFBTimeout:      ttfbTimeout,
+		Timeout:          timeout,
 	}, nil
 }
 
-func (po *ProxyOptions) Merge(o *ProxyOptions) *ProxyOptions {
-	if o.ConnectTimeout > 0 {
-		po.ConnectTimeout = o.ConnectTimeout
-	}
-
-	if o.Timeout > 0 {
-		po.Timeout = o.ConnectTimeout
-	}
-
-	if o.TTFBTimeout > 0 {
-		po.TTFBTimeout = o.TTFBTimeout
-	}
-
-	if len(o.Context) > 0 {
-		po.Context = append(po.Context, o.Context...)
-	}
-
-	if o.CORS != nil {
-		po.CORS = o.CORS
-	}
+func (po *ProxyOptions) Hash() string {
+	h := sha256.New()
+	h.Write([]byte(fmt.Sprintf("%v", po)))
+	return fmt.Sprintf("%x", h.Sum(nil))
+}
 
-	if o.RequestBodyLimit != po.RequestBodyLimit {
-		po.RequestBodyLimit = o.RequestBodyLimit
+// parseDuration sets the target value if the given duration string is not empty.
+func parseDuration(src string, target *time.Duration) error {
+	d, err := time.ParseDuration(src)
+	if src != "" && err != nil {
+		return err
 	}
-
-	return po
+	*target = d
+	return nil
 }
diff --git a/main.go b/main.go
index 96b78601..2587e204 100644
--- a/main.go
+++ b/main.go
@@ -5,13 +5,12 @@ package main
 import (
 	"flag"
 	"os"
-	"path"
 
 	"github.com/sirupsen/logrus"
 
 	"github.com/avenga/couper/command"
 	"github.com/avenga/couper/config"
-	"github.com/avenga/couper/config/env"
+	"github.com/avenga/couper/config/configload"
 	"github.com/avenga/couper/config/runtime"
 )
 
@@ -28,33 +27,30 @@ func main() {
 		os.Exit(1)
 	}
 
-	runtimeConf := runtime.NewConfig(nil)
+	var filePath, logFormat string
 	set := flag.NewFlagSet("global", flag.ContinueOnError)
-	set.StringVar(&runtimeConf.File, "f", runtimeConf.File, "-f ./couper.hcl")
-	set.StringVar(&runtimeConf.LogFormat, "log-format", runtimeConf.LogFormat, "-log-format=common")
+	set.StringVar(&filePath, "f", config.DefaultFileName, "-f ./couper.hcl")
+	set.StringVar(&logFormat, "log-format", config.DefaultSettings.LogFormat, "-log-format=common")
 	err := set.Parse(args.Filter(set))
 	if err != nil {
 		logrus.WithFields(fields).Fatal(err)
 	}
-	envConf := &runtime.Config{}
-	env.Decode(envConf)
-	runtimeConf = runtimeConf.Merge(envConf)
 
-	logger := newLogger(runtimeConf.LogFormat).WithFields(fields)
+	logger := newLogger(logFormat).WithFields(fields)
 
-	wd, err := runtime.SetWorkingDirectory(runtimeConf.File)
+	confFile, err := configload.LoadFile(filePath)
 	if err != nil {
 		logger.Fatal(err)
 	}
-	logger.Infof("working directory: %s", wd)
 
-	gatewayConf, err := config.LoadFile(path.Base(runtimeConf.File))
+	wd, err := os.Getwd()
 	if err != nil {
 		logger.Fatal(err)
 	}
+	logger.Infof("working directory: %s", wd)
 
 	var exitCode int
-	if err = command.NewCommand(args[0]).Execute(args, gatewayConf, logger); err != nil {
+	if err = command.NewCommand(args[0]).Execute(args, confFile, logger); err != nil {
 		logger.Error(err)
 		exitCode = 1
 	}
diff --git a/server/http.go b/server/http.go
index 7c76b9b0..9eb89b41 100644
--- a/server/http.go
+++ b/server/http.go
@@ -11,6 +11,7 @@ import (
 	uuid "github.com/satori/go.uuid"
 	"github.com/sirupsen/logrus"
 
+	"github.com/avenga/couper/config"
 	"github.com/avenga/couper/config/env"
 	"github.com/avenga/couper/config/request"
 	"github.com/avenga/couper/config/runtime"
@@ -23,7 +24,8 @@ import (
 type HTTPServer struct {
 	accessLog  *logging.AccessLog
 	commandCtx context.Context
-	config     *runtime.HTTPConfig
+	settings   *config.Settings
+	timings    *runtime.HTTPTimings
 	listener   net.Listener
 	log        logrus.FieldLogger
 	mux        *Mux
@@ -34,29 +36,25 @@ type HTTPServer struct {
 }
 
 // NewServerList creates a list of all configured HTTP server.
-func NewServerList(cmdCtx context.Context, log logrus.FieldLogger, conf *runtime.HTTPConfig, srvConf *runtime.ServerConfiguration) ([]*HTTPServer, func()) {
+func NewServerList(cmdCtx context.Context, log logrus.FieldLogger, settings *config.Settings, timings *runtime.HTTPTimings, srvConf runtime.ServerConfiguration) ([]*HTTPServer, func()) {
 	var list []*HTTPServer
 
-	for port, srvMux := range srvConf.PortOptions {
-		list = append(list, New(cmdCtx, log, conf, port, srvMux))
+	for port, srvMux := range srvConf {
+		list = append(list, New(cmdCtx, log, settings, timings, port, srvMux))
 	}
 
 	handleShutdownFn := func() {
 		<-cmdCtx.Done()
-		time.Sleep(conf.Timings.ShutdownDelay + conf.Timings.ShutdownTimeout) // wait for max amount, TODO: feedback per server
+		time.Sleep(timings.ShutdownDelay + timings.ShutdownTimeout) // wait for max amount, TODO: feedback per server
 	}
 
 	return list, handleShutdownFn
 }
 
 // New creates a configured HTTP server.
-func New(cmdCtx context.Context, log logrus.FieldLogger, conf *runtime.HTTPConfig, p runtime.Port, muxOpts *runtime.MuxOptions) *HTTPServer {
-	if conf == nil {
-		log.Fatal("missing httpConfig")
-	}
-
+func New(cmdCtx context.Context, log logrus.FieldLogger, settings *config.Settings, timings *runtime.HTTPTimings, p runtime.Port, muxOpts *runtime.MuxOptions) *HTTPServer {
 	var uidFn func() string
-	if conf.RequestIDFormat == "uuid4" {
+	if settings.RequestIDFormat == "uuid4" {
 		uidFn = func() string {
 			return uuid.NewV4().String()
 		}
@@ -73,24 +71,25 @@ func New(cmdCtx context.Context, log logrus.FieldLogger, conf *runtime.HTTPConfi
 	shutdownCh := make(chan struct{})
 
 	mux := NewMux(muxOpts)
-	mux.MustAddRoute(http.MethodGet, conf.HealthPath, handler.NewHealthCheck(conf.HealthPath, shutdownCh))
+	mux.MustAddRoute(http.MethodGet, settings.HealthPath, handler.NewHealthCheck(settings.HealthPath, shutdownCh))
 
 	httpSrv := &HTTPServer{
 		accessLog:  logging.NewAccessLog(&logConf, log),
 		commandCtx: cmdCtx,
-		config:     conf,
 		log:        log,
 		mux:        mux,
 		port:       p.String(),
+		settings:   settings,
 		shutdownCh: shutdownCh,
+		timings:    timings,
 		uidFn:      uidFn,
 	}
 
 	srv := &http.Server{
 		Addr:              ":" + p.String(),
 		Handler:           httpSrv,
-		IdleTimeout:       conf.Timings.IdleTimeout,
-		ReadHeaderTimeout: conf.Timings.ReadHeaderTimeout,
+		IdleTimeout:       timings.IdleTimeout,
+		ReadHeaderTimeout: timings.ReadHeaderTimeout,
 	}
 
 	httpSrv.srv = srv
@@ -137,8 +136,8 @@ func (s *HTTPServer) listenForCtx() {
 	select {
 	case <-s.commandCtx.Done():
 		logFields := logrus.Fields{
-			"delay":    s.config.Timings.ShutdownDelay.String(),
-			"deadline": s.config.Timings.ShutdownTimeout.String(),
+			"delay":    s.timings.ShutdownDelay.String(),
+			"deadline": s.timings.ShutdownTimeout.String(),
 		}
 
 		s.log.WithFields(logFields).Warn("shutting down")
@@ -150,8 +149,8 @@ func (s *HTTPServer) listenForCtx() {
 			return
 		}
 
-		time.Sleep(s.config.Timings.ShutdownDelay)
-		ctx, cancel := context.WithTimeout(context.Background(), s.config.Timings.ShutdownTimeout)
+		time.Sleep(s.timings.ShutdownDelay)
+		ctx, cancel := context.WithTimeout(context.Background(), s.timings.ShutdownTimeout)
 		defer cancel()
 		if err := s.srv.Shutdown(ctx); err != nil {
 			s.log.WithFields(logFields).Error(err)
@@ -185,7 +184,7 @@ func (s *HTTPServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
 // the xfh setting and listener port to be prepared for the http multiplexer.
 func (s *HTTPServer) getHost(req *http.Request) string {
 	host := req.Host
-	if s.config.UseXFH {
+	if s.settings.XForwardedHost {
 		host = req.Header.Get("X-Forwarded-Host")
 	}
 
diff --git a/vendor/github.com/hashicorp/hcl/v2/hclparse/parser.go b/vendor/github.com/hashicorp/hcl/v2/hclparse/parser.go
new file mode 100644
index 00000000..1dc2eccd
--- /dev/null
+++ b/vendor/github.com/hashicorp/hcl/v2/hclparse/parser.go
@@ -0,0 +1,135 @@
+// Package hclparse has the main API entry point for parsing both HCL native
+// syntax and HCL JSON.
+//
+// The main HCL package also includes SimpleParse and SimpleParseFile which
+// can be a simpler interface for the common case where an application just
+// needs to parse a single file. The gohcl package simplifies that further
+// in its SimpleDecode function, which combines hcl.SimpleParse with decoding
+// into Go struct values
+//
+// Package hclparse, then, is useful for applications that require more fine
+// control over parsing or which need to load many separate files and keep
+// track of them for possible error reporting or other analysis.
+package hclparse
+
+import (
+	"fmt"
+	"io/ioutil"
+
+	"github.com/hashicorp/hcl/v2"
+	"github.com/hashicorp/hcl/v2/hclsyntax"
+	"github.com/hashicorp/hcl/v2/json"
+)
+
+// NOTE: This is the public interface for parsing. The actual parsers are
+// in other packages alongside this one, with this package just wrapping them
+// to provide a unified interface for the caller across all supported formats.
+
+// Parser is the main interface for parsing configuration files. As well as
+// parsing files, a parser also retains a registry of all of the files it
+// has parsed so that multiple attempts to parse the same file will return
+// the same object and so the collected files can be used when printing
+// diagnostics.
+//
+// Any diagnostics for parsing a file are only returned once on the first
+// call to parse that file. Callers are expected to collect up diagnostics
+// and present them together, so returning diagnostics for the same file
+// multiple times would create a confusing result.
+type Parser struct {
+	files map[string]*hcl.File
+}
+
+// NewParser creates a new parser, ready to parse configuration files.
+func NewParser() *Parser {
+	return &Parser{
+		files: map[string]*hcl.File{},
+	}
+}
+
+// ParseHCL parses the given buffer (which is assumed to have been loaded from
+// the given filename) as a native-syntax configuration file and returns the
+// hcl.File object representing it.
+func (p *Parser) ParseHCL(src []byte, filename string) (*hcl.File, hcl.Diagnostics) {
+	if existing := p.files[filename]; existing != nil {
+		return existing, nil
+	}
+
+	file, diags := hclsyntax.ParseConfig(src, filename, hcl.Pos{Byte: 0, Line: 1, Column: 1})
+	p.files[filename] = file
+	return file, diags
+}
+
+// ParseHCLFile reads the given filename and parses it as a native-syntax HCL
+// configuration file. An error diagnostic is returned if the given file
+// cannot be read.
+func (p *Parser) ParseHCLFile(filename string) (*hcl.File, hcl.Diagnostics) {
+	if existing := p.files[filename]; existing != nil {
+		return existing, nil
+	}
+
+	src, err := ioutil.ReadFile(filename)
+	if err != nil {
+		return nil, hcl.Diagnostics{
+			{
+				Severity: hcl.DiagError,
+				Summary:  "Failed to read file",
+				Detail:   fmt.Sprintf("The configuration file %q could not be read.", filename),
+			},
+		}
+	}
+
+	return p.ParseHCL(src, filename)
+}
+
+// ParseJSON parses the given JSON buffer (which is assumed to have been loaded
+// from the given filename) and returns the hcl.File object representing it.
+func (p *Parser) ParseJSON(src []byte, filename string) (*hcl.File, hcl.Diagnostics) {
+	if existing := p.files[filename]; existing != nil {
+		return existing, nil
+	}
+
+	file, diags := json.Parse(src, filename)
+	p.files[filename] = file
+	return file, diags
+}
+
+// ParseJSONFile reads the given filename and parses it as JSON, similarly to
+// ParseJSON. An error diagnostic is returned if the given file cannot be read.
+func (p *Parser) ParseJSONFile(filename string) (*hcl.File, hcl.Diagnostics) {
+	if existing := p.files[filename]; existing != nil {
+		return existing, nil
+	}
+
+	file, diags := json.ParseFile(filename)
+	p.files[filename] = file
+	return file, diags
+}
+
+// AddFile allows a caller to record in a parser a file that was parsed some
+// other way, thus allowing it to be included in the registry of sources.
+func (p *Parser) AddFile(filename string, file *hcl.File) {
+	p.files[filename] = file
+}
+
+// Sources returns a map from filenames to the raw source code that was
+// read from them. This is intended to be used, for example, to print
+// diagnostics with contextual information.
+//
+// The arrays underlying the returned slices should not be modified.
+func (p *Parser) Sources() map[string][]byte {
+	ret := make(map[string][]byte)
+	for fn, f := range p.files {
+		ret[fn] = f.Bytes
+	}
+	return ret
+}
+
+// Files returns a map from filenames to the File objects produced from them.
+// This is intended to be used, for example, to print diagnostics with
+// contextual information.
+//
+// The returned map and all of the objects it refers to directly or indirectly
+// must not be modified.
+func (p *Parser) Files() map[string]*hcl.File {
+	return p.files
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 0e60ffb4..0aa8d662 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -33,6 +33,7 @@ github.com/google/go-cmp/cmp/internal/value
 github.com/hashicorp/hcl/v2
 github.com/hashicorp/hcl/v2/ext/customdecode
 github.com/hashicorp/hcl/v2/gohcl
+github.com/hashicorp/hcl/v2/hclparse
 github.com/hashicorp/hcl/v2/hclsimple
 github.com/hashicorp/hcl/v2/hclsyntax
 github.com/hashicorp/hcl/v2/hcltest
@@ -81,7 +82,7 @@ golang.org/x/text/secure/bidirule
 golang.org/x/text/transform
 golang.org/x/text/unicode/bidi
 golang.org/x/text/unicode/norm
-# golang.org/x/tools v0.0.0-20201206230334-368bee879bfd
+# golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
 ## explicit
 # gopkg.in/yaml.v2 v2.3.0
 gopkg.in/yaml.v2


Test patch:
diff --git a/config/backend_test.go b/config/backend_test.go
deleted file mode 100644
index 77dfc1ba..00000000
--- a/config/backend_test.go
+++ /dev/null
@@ -1,62 +0,0 @@
-package config
-
-import (
-	"reflect"
-	"testing"
-
-	"github.com/hashicorp/hcl/v2"
-)
-
-func TestBackend_Merge(t *testing.T) {
-	type args struct {
-		other *Backend
-	}
-	tests := []struct {
-		name   string
-		fields Backend
-		args   args
-		want   *Backend
-	}{
-		{"nil", Backend{}, args{nil}, nil},
-		{"empty", Backend{}, args{&Backend{}}, &Backend{}},
-		{"left", Backend{
-			Timeout: "s", ConnectTimeout: "s", Remain: hcl.EmptyBody(),
-		}, args{&Backend{}}, &Backend{
-			Timeout: "s", ConnectTimeout: "s", Remain: hcl.EmptyBody(),
-		}},
-		{"right", Backend{}, args{&Backend{
-			Timeout: "s", ConnectTimeout: "s", Remain: hcl.EmptyBody(),
-		}}, &Backend{
-			Timeout: "s", ConnectTimeout: "s", Remain: hcl.EmptyBody(),
-		}},
-		{"override", Backend{
-			Timeout: "s", ConnectTimeout: "s", RequestBodyLimit: "2M", TTFBTimeout: "t", Remain: hcl.EmptyBody(),
-		}, args{&Backend{
-			Timeout: "m", ConnectTimeout: "h", RequestBodyLimit: "20M", TTFBTimeout: "o", Remain: hcl.EmptyBody(),
-		}}, &Backend{
-			Timeout: "m", ConnectTimeout: "h", RequestBodyLimit: "20M", TTFBTimeout: "o", Remain: hcl.EmptyBody(),
-		}},
-		{"partial override", Backend{
-			Timeout: "e", ConnectTimeout: "f", TTFBTimeout: "t", Remain: hcl.EmptyBody(),
-		}, args{&Backend{
-			ConnectTimeout: "d",
-		}}, &Backend{
-			Timeout: "e", ConnectTimeout: "d", TTFBTimeout: "t", Remain: hcl.EmptyBody(),
-		}},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			b := &Backend{
-				ConnectTimeout:   tt.fields.ConnectTimeout,
-				Name:             tt.fields.Name,
-				Remain:           tt.fields.Remain,
-				RequestBodyLimit: tt.fields.RequestBodyLimit,
-				Timeout:          tt.fields.Timeout,
-				TTFBTimeout:      tt.fields.TTFBTimeout,
-			}
-			if got, _ := b.Merge(tt.args.other); !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("Merge() = %v, want %v", got, tt.want)
-			}
-		})
-	}
-}
diff --git a/config/gateway_test.go b/config/gateway_test.go
deleted file mode 100644
index decd0337..00000000
--- a/config/gateway_test.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package config_test
-
-import (
-	"fmt"
-	"testing"
-
-	"github.com/hashicorp/hcl/v2/gohcl"
-	"github.com/hashicorp/hcl/v2/hclwrite"
-
-	"github.com/avenga/couper/config"
-)
-
-func TestWriteGateway(t *testing.T) {
-	conf := config.Gateway{
-		Server: []*config.Server{
-			{
-				Name: "wurst",
-				API: &config.Api{
-					BasePath: "/hans/v1",
-					Endpoint: []*config.Endpoint{
-						{
-							Pattern: "/proxy/",
-						},
-					},
-				},
-			},
-		},
-	}
-	f := hclwrite.NewEmptyFile()
-	gohcl.EncodeIntoBody(&conf, f.Body())
-	fmt.Printf("%s", f.Bytes())
-}
diff --git a/config/runtime/server_internal_test.go b/config/runtime/server_internal_test.go
index 475f0934..ce2c2590 100644
--- a/config/runtime/server_internal_test.go
+++ b/config/runtime/server_internal_test.go
@@ -39,7 +39,7 @@ func TestServer_isUnique(t *testing.T) {
 
 func TestServer_validatePortHosts(t *testing.T) {
 	type args struct {
-		conf           *config.Gateway
+		conf           *config.CouperFile
 		configuredPort int
 	}
 	tests := []struct {
@@ -50,7 +50,7 @@ func TestServer_validatePortHosts(t *testing.T) {
 		{
 			"Same host/port in one server",
 			args{
-				&config.Gateway{
+				&config.CouperFile{
 					Server: []*config.Server{
 						{Hosts: []string{"*", "*", "*:9090", "*:9090", "*:8080"}},
 					},
@@ -61,7 +61,7 @@ func TestServer_validatePortHosts(t *testing.T) {
 		{
 			"Same host/port in two servers with *",
 			args{
-				&config.Gateway{
+				&config.CouperFile{
 					Server: []*config.Server{
 						{Hosts: []string{"*"}},
 						{Hosts: []string{"*"}},
@@ -73,7 +73,7 @@ func TestServer_validatePortHosts(t *testing.T) {
 		{
 			"Same host/port in two servers with *:<port>",
 			args{
-				&config.Gateway{
+				&config.CouperFile{
 					Server: []*config.Server{
 						{Hosts: []string{"*:8080"}},
 						{Hosts: []string{"*:8080"}},
@@ -85,7 +85,7 @@ func TestServer_validatePortHosts(t *testing.T) {
 		{
 			"Same host/port in two servers with example.com",
 			args{
-				&config.Gateway{
+				&config.CouperFile{
 					Server: []*config.Server{
 						{Hosts: []string{"example.com", "couper.io"}},
 						{Hosts: []string{"example.com", "couper.io"}},
@@ -97,7 +97,7 @@ func TestServer_validatePortHosts(t *testing.T) {
 		{
 			"Same host/port in two servers with example.com:<port>",
 			args{
-				&config.Gateway{
+				&config.CouperFile{
 					Server: []*config.Server{
 						{Hosts: []string{"example.com:9090"}},
 						{Hosts: []string{"example.com:9090"}},
@@ -109,7 +109,7 @@ func TestServer_validatePortHosts(t *testing.T) {
 		{
 			"Same port w/ different host in two servers",
 			args{
-				&config.Gateway{
+				&config.CouperFile{
 					Server: []*config.Server{
 						{Hosts: []string{"*", "example.com:9090"}},
 						{Hosts: []string{"couper.io:9090"}},
@@ -121,7 +121,7 @@ func TestServer_validatePortHosts(t *testing.T) {
 		{
 			"Host is mandatory for multiple servers",
 			args{
-				&config.Gateway{
+				&config.CouperFile{
 					Server: []*config.Server{
 						{Hosts: []string{"*"}},
 						{},
@@ -133,7 +133,7 @@ func TestServer_validatePortHosts(t *testing.T) {
 		{
 			"Host is optional for single server",
 			args{
-				&config.Gateway{
+				&config.CouperFile{
 					Server: []*config.Server{
 						{},
 					},
diff --git a/eval/context_test.go b/eval/context_test.go
index 8ab041d1..26931417 100644
--- a/eval/context_test.go
+++ b/eval/context_test.go
@@ -111,7 +111,7 @@ func TestNewHTTPContext(t *testing.T) {
 
 			// since the proxy prepares the getBody rewind:
 			proxy, err := handler.NewProxy(&handler.ProxyOptions{
-				Context:          []hcl.Body{hclBody},
+				Context:          hclBody,
 				RequestBodyLimit: 256,
 				CORS:             &handler.CORSOptions{},
 			}, log.WithContext(nil), srvOpts, nil)
diff --git a/handler/openapi_validator_test.go b/handler/openapi_validator_test.go
index f5c20e01..73c0754b 100644
--- a/handler/openapi_validator_test.go
+++ b/handler/openapi_validator_test.go
@@ -2,7 +2,6 @@ package handler_test
 
 import (
 	"context"
-	"fmt"
 	"io"
 	"io/ioutil"
 	"net/http"
@@ -11,10 +10,14 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/avenga/couper/config/body"
+	"github.com/hashicorp/hcl/v2"
+	"github.com/hashicorp/hcl/v2/hcltest"
+	"github.com/zclconf/go-cty/cty"
+
 	logrustest "github.com/sirupsen/logrus/hooks/test"
 
 	"github.com/avenga/couper/config"
-	"github.com/avenga/couper/config/runtime"
 	"github.com/avenga/couper/config/runtime/server"
 	"github.com/avenga/couper/errors"
 	"github.com/avenga/couper/eval"
@@ -45,11 +48,20 @@ func TestOpenAPIValidator_ValidateRequest(t *testing.T) {
 
 	log, hook := logrustest.NewNullLogger()
 
-	beConf, _ := runtime.DefaultBackendConf.Merge(&config.Backend{OpenAPI: &config.OpenAPI{
-		File: filepath.Join("testdata/validation/backend_01_openapi.yaml"),
-	}})
+	beConf := &config.Backend{
+		Remain: body.New(&hcl.BodyContent{Attributes: hcl.Attributes{
+			"origin": &hcl.Attribute{
+				Name: "origin",
+				Expr: hcltest.MockExprLiteral(cty.StringVal(origin.URL)),
+			},
+		}}),
+		OpenAPI: []*config.OpenAPI{&config.OpenAPI{
+			File: filepath.Join("testdata/validation/backend_01_openapi.yaml"),
+		}},
+		RequestBodyLimit: "64MiB",
+	}
 
-	proxyOpts, err := handler.NewProxyOptions(beConf, &handler.CORSOptions{}, helper.NewProxyContext(fmt.Sprintf(`origin = "http://%s"`, origin.Listener.Addr())))
+	proxyOpts, err := handler.NewProxyOptions(beConf, &handler.CORSOptions{})
 	helper.Must(err)
 
 	backend, err := handler.NewProxy(proxyOpts, log.WithContext(context.Background()), &server.Options{APIErrTpl: errors.DefaultJSON}, eval.NewENVContext(nil))
diff --git a/handler/proxy_test.go b/handler/proxy_test.go
index e239ffcd..0b8f4e95 100644
--- a/handler/proxy_test.go
+++ b/handler/proxy_test.go
@@ -21,6 +21,7 @@ import (
 	logrustest "github.com/sirupsen/logrus/hooks/test"
 
 	"github.com/avenga/couper/config"
+	"github.com/avenga/couper/config/configload"
 	"github.com/avenga/couper/config/request"
 	"github.com/avenga/couper/config/runtime/server"
 	"github.com/avenga/couper/errors"
@@ -31,7 +32,6 @@ import (
 )
 
 func TestProxy_ServeHTTP_Timings(t *testing.T) {
-	t.Skip("todo: fixme")
 	origin := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
 		if req.Method == http.MethodHead {
 			time.Sleep(time.Second * 2) // > ttfb proxy settings
@@ -47,14 +47,15 @@ func TestProxy_ServeHTTP_Timings(t *testing.T) {
 		expectedStatus int
 	}{
 		{"with zero timings", &handler.ProxyOptions{Context: test.NewRemainContext("origin", origin.URL)}, httptest.NewRequest(http.MethodGet, "http://1.2.3.4/", nil), http.StatusNoContent},
-		{"with overall timeout", &handler.ProxyOptions{Context: test.NewRemainContext("origin", "http://1.2.3.4/"), Timeout: time.Second}, httptest.NewRequest(http.MethodGet, "http://1.2.3.4/", nil), http.StatusBadGateway},
-		{"with connect timeout", &handler.ProxyOptions{Context: test.NewRemainContext("origin", "http://blackhole.webpagetest.org/"), ConnectTimeout: time.Second}, httptest.NewRequest(http.MethodGet, "http://1.2.3.4/", nil), http.StatusBadGateway},
-		{"with ttfb timeout", &handler.ProxyOptions{Context: test.NewRemainContext("origin", origin.URL), TTFBTimeout: time.Second}, httptest.NewRequest(http.MethodHead, "http://1.2.3.4/", nil), http.StatusBadGateway},
+		{"with overall timeout", &handler.ProxyOptions{Context: test.NewRemainContext("origin", "http://1.2.3.4/"), Timeout: time.Second}, httptest.NewRequest(http.MethodGet, "http://1.2.3.5/", nil), http.StatusBadGateway},
+		{"with connect timeout", &handler.ProxyOptions{Context: test.NewRemainContext("origin", "http://blackhole.webpagetest.org/"), ConnectTimeout: time.Second}, httptest.NewRequest(http.MethodGet, "http://1.2.3.6/", nil), http.StatusBadGateway},
+		{"with ttfb timeout", &handler.ProxyOptions{Context: test.NewRemainContext("origin", origin.URL), TTFBTimeout: time.Second}, httptest.NewRequest(http.MethodHead, "http://1.2.3.7/", nil), http.StatusBadGateway},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			logger, hook := logrustest.NewNullLogger()
-			p, err := handler.NewProxy(tt.options, logger.WithContext(nil), nil, eval.NewENVContext(nil))
+			srvOpts, _ := server.NewServerOptions(nil)
+			p, err := handler.NewProxy(tt.options, logger.WithContext(nil), srvOpts, eval.NewENVContext(nil))
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -571,7 +572,7 @@ func TestProxy_director(t *testing.T) {
 				t.Fatal(err)
 			}
 
-			attr, _ := hclContext[0].JustAttributes()
+			attr, _ := hclContext.JustAttributes()
 			hostnameExp, ok := attr["hostname"]
 
 			if !ok && tt.expReq.Host != req.Host {
@@ -592,16 +593,10 @@ func TestProxy_director(t *testing.T) {
 }
 
 func TestProxy_ServeHTTP_Eval(t *testing.T) {
-	type fields struct {
-		evalContext *hcl.EvalContext
-		options     *handler.ProxyOptions
-	}
-
 	type header map[string]string
 
 	type testCase struct {
 		name       string
-		fields     fields
 		hcl        string
 		method     string
 		body       io.Reader
@@ -627,19 +622,12 @@ func TestProxy_ServeHTTP_Eval(t *testing.T) {
 	}))
 	defer origin.Close()
 
-	opts := &handler.ProxyOptions{
-		BackendName:      "test-origin",
-		Context:          test.NewRemainContext("origin", "http://"+origin.Listener.Addr().String()),
-		CORS:             &handler.CORSOptions{},
-		RequestBodyLimit: 10,
-	}
-
 	tests := []testCase{
-		{"GET use req.Header", fields{baseCtx, opts}, `
+		{"GET use req.Header", `
 		set_response_headers = {
 			X-Method = req.method
 		}`, http.MethodGet, nil, header{"X-Method": http.MethodGet}, false},
-		{"POST use req.post", fields{baseCtx, opts}, `
+		{"POST use req.post", `
 		set_response_headers = {
 			X-Method = req.method
 			X-Post = req.post.foo
@@ -657,8 +645,19 @@ func TestProxy_ServeHTTP_Eval(t *testing.T) {
 			if err != nil {
 				t.Fatal(err)
 			}
-			tt.fields.options.Context = append(tt.fields.options.Context, remain.Inline)
-			p, err := handler.NewProxy(tt.fields.options, log.WithContext(context.Background()), nil, tt.fields.evalContext)
+
+			p, err := handler.NewProxy(&handler.ProxyOptions{
+				RequestBodyLimit: 10,
+				Context: configload.MergeBodies([]hcl.Body{
+					test.NewRemainContext("origin", "http://"+origin.Listener.Addr().String()),
+					remain.Inline,
+				}),
+			}, log.WithContext(context.Background()),
+				&server.Options{
+					APIErrTpl: errors.DefaultJSON,
+				},
+				baseCtx)
+
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -699,25 +698,30 @@ func TestProxy_SetGetBody_LimitBody_Roundtrip(t *testing.T) {
 
 	type testCase struct {
 		name    string
-		limit   int64
+		limit   string
 		payload string
 		wantErr error
 	}
 
 	for _, testcase := range []testCase{
-		{"/w well sized limit", 12, "content", nil},
-		{"/w zero limit", 0, "01", errors.APIReqBodySizeExceeded},
-		{"/w limit /w oversize body", 4, "12345", errors.APIReqBodySizeExceeded},
+		{"/w well sized limit", "12MiB", "content", nil},
+		{"/w zero limit", "0", "01", errors.APIReqBodySizeExceeded},
+		{"/w limit /w oversize body", "4B", "12345", errors.APIReqBodySizeExceeded},
 	} {
 		t.Run(testcase.name, func(subT *testing.T) {
-			proxy, _, _, closeFn := helper.NewProxy(&handler.ProxyOptions{
-				Context:          helper.NewProxyContext("set_request_headers = { x = req.post }"), // ensure buffering is enabled
+			proxyOpts, err := handler.NewProxyOptions(&config.Backend{
+				Remain:           helper.NewProxyContext("set_request_headers = { x = req.post }"), // ensure buffering is enabled
 				RequestBodyLimit: testcase.limit,
-			})
+			}, nil)
+			if err != nil {
+				subT.Error(err)
+				return
+			}
+			proxy, _, _, closeFn := helper.NewProxy(proxyOpts)
 			closeFn() // unused
 
 			req := httptest.NewRequest(http.MethodPut, "/", bytes.NewBufferString(testcase.payload))
-			err := proxy.SetGetBody(req)
+			err = proxy.SetGetBody(req)
 			if !reflect.DeepEqual(err, testcase.wantErr) {
 				subT.Errorf("Expected '%v', got: '%v'", testcase.wantErr, err)
 			}
@@ -854,13 +858,12 @@ func TestProxy_DisableCompression(t *testing.T) {
 
 	logger, _ := logrustest.NewNullLogger()
 
-	var hclBody []hcl.Body
 	u := seetie.GoToValue(origin.URL)
-	hclBody = append(hclBody, hcltest.MockBody(&hcl.BodyContent{
+	hclBody := hcltest.MockBody(&hcl.BodyContent{
 		Attributes: hcltest.MockAttrs(map[string]hcl.Expression{
 			"origin": hcltest.MockExprLiteral(u),
 		}),
-	}))
+	})
 
 	p, err := handler.NewProxy(
 		&handler.ProxyOptions{Context: hclBody},
@@ -895,13 +898,12 @@ func TestProxy_ModifyAcceptEncoding(t *testing.T) {
 
 	logger, _ := logrustest.NewNullLogger()
 
-	var hclBody []hcl.Body
 	u := seetie.GoToValue(origin.URL)
-	hclBody = append(hclBody, hcltest.MockBody(&hcl.BodyContent{
+	hclBody := hcltest.MockBody(&hcl.BodyContent{
 		Attributes: hcltest.MockAttrs(map[string]hcl.Expression{
 			"origin": hcltest.MockExprLiteral(u),
 		}),
-	}))
+	})
 
 	p, err := handler.NewProxy(
 		&handler.ProxyOptions{Context: hclBody},
@@ -969,8 +971,8 @@ func TestProxy_SetRoundtripContext_Null_Eval(t *testing.T) {
 			evalCtx := eval.NewENVContext(nil)
 
 			proxy, err := handler.NewProxy(&handler.ProxyOptions{
-				Context: append(test.NewRemainContext("origin", "http://"+origin.Listener.Addr().String()),
-					helper.NewProxyContext(tc.remain)...),
+				Context: configload.MergeBodies([]hcl.Body{test.NewRemainContext("origin", "http://"+origin.Listener.Addr().String()),
+					helper.NewProxyContext(tc.remain)}),
 				RequestBodyLimit: 64,
 			}, log.WithContext(context.Background()), &server.Options{APIErrTpl: errors.DefaultJSON}, evalCtx)
 			h.Must(err)
@@ -1065,8 +1067,8 @@ func TestProxy_BufferingOptions(t *testing.T) {
 
 			proxy, err := handler.NewProxy(&handler.ProxyOptions{
 				OpenAPI: tc.apiOptions,
-				Context: append(test.NewRemainContext("origin", "http://"+origin.Listener.Addr().String()),
-					helper.NewProxyContext(tc.remain)...),
+				Context: configload.MergeBodies([]hcl.Body{test.NewRemainContext("origin", "http://"+origin.Listener.Addr().String()),
+					helper.NewProxyContext(tc.remain)}),
 				RequestBodyLimit: 64,
 			}, log.WithContext(context.Background()), &server.Options{APIErrTpl: errors.DefaultJSON}, evalCtx)
 			h.Must(err)
diff --git a/internal/test/hcl.go b/internal/test/hcl.go
index 74e84256..51ff4fb4 100644
--- a/internal/test/hcl.go
+++ b/internal/test/hcl.go
@@ -4,11 +4,13 @@ import (
 	"github.com/hashicorp/hcl/v2"
 	"github.com/hashicorp/hcl/v2/hcltest"
 	"github.com/zclconf/go-cty/cty"
+
+	"github.com/avenga/couper/config/configload"
 )
 
-func NewRemainContext(name, value string) []hcl.Body {
+func NewRemainContext(name, value string) hcl.Body {
 	expr := hcltest.MockExprLiteral(cty.StringVal(value))
-	return []hcl.Body{hcltest.MockBody(&hcl.BodyContent{Attributes: map[string]*hcl.Attribute{
+	return configload.MergeBodies([]hcl.Body{hcltest.MockBody(&hcl.BodyContent{Attributes: map[string]*hcl.Attribute{
 		name: {Name: name, Expr: expr},
-	}})}
+	}})})
 }
diff --git a/internal/test/helper_proxy.go b/internal/test/helper_proxy.go
index 85e620e0..c5c5bc5b 100644
--- a/internal/test/helper_proxy.go
+++ b/internal/test/helper_proxy.go
@@ -9,6 +9,7 @@ import (
 	"github.com/hashicorp/hcl/v2/hclsimple"
 	"github.com/sirupsen/logrus/hooks/test"
 
+	"github.com/avenga/couper/config/configload"
 	"github.com/avenga/couper/eval"
 	"github.com/avenga/couper/handler"
 )
@@ -19,25 +20,19 @@ func (h *Helper) NewProxy(opts *handler.ProxyOptions) (*handler.Proxy, *http.Cli
 	var upstream http.HandlerFunc
 	server := httptest.NewServer(upstream)
 
-	proxyOptions := &handler.ProxyOptions{
-		Context:     h.NewProxyContext(""),
-		BackendName: "HelperUpstream",
-		CORS:        &handler.CORSOptions{},
-	}
-	proxyOptions = proxyOptions.Merge(opts)
-
-	proxy, err := handler.NewProxy(proxyOptions, logger.WithContext(context.Background()), nil, eval.NewENVContext(nil))
+	opts.BackendName = "HelperUpstream"
+	proxy, err := handler.NewProxy(opts, logger.WithContext(context.Background()), nil, eval.NewENVContext(nil))
 	h.Must(err)
 
 	return proxy.(*handler.Proxy), server.Client(), upstream, server.Close
 }
 
-func (h *Helper) NewProxyContext(inlineHCL string) []hcl.Body {
+func (h *Helper) NewProxyContext(inlineHCL string) hcl.Body {
 	type hclBody struct {
 		Inline hcl.Body `hcl:",remain"`
 	}
 
 	var remain hclBody
 	h.Must(hclsimple.Decode(h.tb.Name()+".hcl", []byte(inlineHCL), eval.NewENVContext(nil), &remain))
-	return []hcl.Body{remain.Inline}
+	return configload.MergeBodies([]hcl.Body{remain.Inline})
 }
diff --git a/server/http_integration_test.go b/server/http_integration_test.go
index 4de4aeb0..a0a7b798 100644
--- a/server/http_integration_test.go
+++ b/server/http_integration_test.go
@@ -18,15 +18,12 @@ import (
 	"testing"
 	"time"
 
-	"github.com/hashicorp/hcl/v2/hcltest"
-	"github.com/zclconf/go-cty/cty"
+	"github.com/avenga/couper/config/configload"
 
 	"github.com/sirupsen/logrus"
 	logrustest "github.com/sirupsen/logrus/hooks/test"
 
 	"github.com/avenga/couper/command"
-	"github.com/avenga/couper/config"
-	"github.com/avenga/couper/config/runtime"
 	"github.com/avenga/couper/internal/test"
 )
 
@@ -66,33 +63,9 @@ func teardown() {
 }
 
 func newCouper(file string, helper *test.Helper) (func(), *logrustest.Hook) {
-	_, err := runtime.SetWorkingDirectory(filepath.Join(testWorkingDir, file))
+	gatewayConf, err := configload.LoadFile(filepath.Join(testWorkingDir, file))
 	helper.Must(err)
 
-	gatewayConf, err := config.LoadFile(path.Base(file))
-	helper.Must(err)
-
-	// replace all origins with our test backend addr
-	// TODO: limitation: no support for inline origin changes
-	if gatewayConf.Definitions != nil {
-		for _, backend := range gatewayConf.Definitions.Backend {
-			backendSchema := config.Backend{}.Schema(false)
-			inlineSchema := config.Backend{}.Schema(true)
-			backendSchema.Attributes = append(backendSchema.Attributes, inlineSchema.Attributes...)
-			content, diags := backend.Remain.Content(backendSchema)
-			if diags != nil && diags.HasErrors() {
-				helper.Must(diags)
-			}
-
-			if _, ok := content.Attributes["origin"]; !ok {
-				helper.Must(fmt.Errorf("backend requires an origin value"))
-			}
-
-			content.Attributes["origin"].Expr = hcltest.MockExprLiteral(cty.StringVal(testBackend.Addr()))
-			backend.Remain = hcltest.MockBody(content)
-		}
-	}
-
 	log, hook := logrustest.NewNullLogger()
 
 	ctx, cancel := context.WithCancel(test.NewContext(context.Background()))
@@ -100,10 +73,15 @@ func newCouper(file string, helper *test.Helper) (func(), *logrustest.Hook) {
 		cancel()
 		time.Sleep(time.Second / 2)
 	}
+	shutdownFn := func() {
+		cleanup(cancelFn, helper)
+	}
+
 	//log.Out = os.Stdout
 
 	go func() {
 		if err := command.NewRun(ctx).Execute([]string{file}, gatewayConf, log.WithContext(ctx)); err != nil {
+			shutdownFn()
 			panic(err)
 		}
 	}()
@@ -117,7 +95,7 @@ func newCouper(file string, helper *test.Helper) (func(), *logrustest.Hook) {
 	}
 
 	hook.Reset() // no startup logs
-	return cancelFn, hook
+	return shutdownFn, hook
 }
 
 func newClient() *http.Client {
@@ -136,12 +114,12 @@ func newClient() *http.Client {
 	}
 }
 
-func cleanup(shutdown func(), t *testing.T) {
+func cleanup(shutdown func(), helper *test.Helper) {
 	shutdown()
 
 	err := os.Chdir(testWorkingDir)
 	if err != nil {
-		t.Fatal(err)
+		helper.Must(err)
 	}
 }
 
@@ -363,7 +341,7 @@ func TestHTTPServer_ServeHTTP(t *testing.T) {
 			})
 		}
 
-		cleanup(shutdown, t)
+		shutdown()
 	}
 }
 
@@ -393,7 +371,7 @@ func TestHTTPServer_HostHeader(t *testing.T) {
 		}
 	})
 
-	cleanup(shutdown, t)
+	shutdown()
 }
 
 func TestHTTPServer_HostHeader2(t *testing.T) {
@@ -429,7 +407,7 @@ func TestHTTPServer_HostHeader2(t *testing.T) {
 		}
 	})
 
-	cleanup(shutdown, t)
+	shutdown()
 }
 
 func TestHTTPServer_XFHHeader(t *testing.T) {
@@ -469,7 +447,7 @@ func TestHTTPServer_XFHHeader(t *testing.T) {
 		}
 	})
 
-	cleanup(shutdown, t)
+	shutdown()
 }
 
 func TestHTTPServer_Gzip(t *testing.T) {
@@ -536,7 +514,7 @@ func TestHTTPServer_Gzip(t *testing.T) {
 		})
 	}
 
-	cleanup(shutdown, t)
+	shutdown()
 }
 
 func TestHTTPServer_QueryParams(t *testing.T) {
@@ -582,7 +560,7 @@ func TestHTTPServer_QueryParams(t *testing.T) {
 				"def_string":  []string{"str", "str"},
 				"def":         []string{"def", "def"},
 				"foo":         []string{""},
-				"xxx":         []string{"ddd", "zzz", "aaa", "bbb", "eee", "ccc"},
+				"xxx":         []string{"aaa", "bbb"},
 			},
 			Path: "/",
 		}},
@@ -639,7 +617,7 @@ func TestHTTPServer_QueryParams(t *testing.T) {
 			}
 		})
 
-		cleanup(shutdown, t)
+		shutdown()
 	}
 }
 
@@ -698,7 +676,7 @@ func TestHTTPServer_Endpoint_Evaluation(t *testing.T) {
 		})
 	}
 
-	cleanup(shutdown, t)
+	shutdown()
 }
 
 func TestHTTPServer_Endpoint_Evaluation_Inheritance(t *testing.T) {
@@ -761,7 +739,7 @@ func TestHTTPServer_Endpoint_Evaluation_Inheritance(t *testing.T) {
 				}
 			})
 		}
-		cleanup(shutdown, t)
+		shutdown()
 	}
 }
 
@@ -780,5 +758,5 @@ func TestHTTPServer_Endpoint_Evaluation_Inheritance_Backend_Block(t *testing.T)
 	if res.StatusCode != http.StatusBadRequest {
 		t.Error("Expected a bad request without required query param")
 	}
-	cleanup(shutdown, t)
+	shutdown()
 }
diff --git a/server/http_test.go b/server/http_test.go
index a4e541f1..5b28c09e 100644
--- a/server/http_test.go
+++ b/server/http_test.go
@@ -14,9 +14,11 @@ import (
 	"text/template"
 	"time"
 
+	"github.com/avenga/couper/config"
+	"github.com/avenga/couper/config/configload"
+
 	logrustest "github.com/sirupsen/logrus/hooks/test"
 
-	"github.com/avenga/couper/config"
 	"github.com/avenga/couper/config/runtime"
 	"github.com/avenga/couper/internal/test"
 	"github.com/avenga/couper/server"
@@ -57,20 +59,18 @@ func TestHTTPServer_ServeHTTP_Files(t *testing.T) {
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
 
-	httpConf := runtime.NewHTTPConfig(nil)
-	httpConf.ListenPort = 0 // random
-
-	conf, err := config.LoadBytes(confBytes.Bytes(), "conf_test.hcl")
+	conf, err := configload.LoadBytes(confBytes.Bytes(), "conf_test.hcl")
 	helper.Must(err)
+	conf.Settings.DefaultPort = 0
 
-	srvConf, err := runtime.NewServerConfiguration(conf, httpConf, log.WithContext(nil))
+	srvConf, err := runtime.NewServerConfiguration(conf, log.WithContext(nil))
 	helper.Must(err)
 
 	spaContent, err := ioutil.ReadFile(conf.Server[0].Spa.BootstrapFile)
 	helper.Must(err)
 
-	port := runtime.Port(httpConf.ListenPort)
-	gw := server.New(ctx, log.WithContext(ctx), httpConf, port, srvConf.PortOptions[port])
+	port := runtime.Port(conf.Settings.DefaultPort)
+	gw := server.New(ctx, log.WithContext(ctx), conf.Settings, &runtime.DefaultTimings, port, srvConf[port])
 	gw.Listen()
 	defer gw.Close()
 
@@ -142,8 +142,7 @@ func TestHTTPServer_ServeHTTP_Files2(t *testing.T) {
 
 	confBytes := &bytes.Buffer{}
 	err = tpl.Execute(confBytes, map[string]string{
-		"origin":   "http://" + originBackend.Listener.Addr().String(),
-		"hostname": expectedAPIHost,
+		"origin": "http://" + originBackend.Listener.Addr().String(),
 	})
 	helper.Must(err)
 
@@ -153,21 +152,17 @@ func TestHTTPServer_ServeHTTP_Files2(t *testing.T) {
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
 
-	httpConf := runtime.NewHTTPConfig(nil)
-	httpConf.ListenPort = 0 // random
-
-	conf, err := config.LoadBytes(confBytes.Bytes(), "conf_fileserving.hcl")
+	conf, err := configload.LoadBytes(confBytes.Bytes(), "conf_fileserving.hcl")
 	helper.Must(err)
 
 	error404Content := []byte("<html><body><h1>3001: Files route not found: My custom error template</h1></body></html>")
 	spaContent, err := ioutil.ReadFile(conf.Server[0].Spa.BootstrapFile)
 	helper.Must(err)
 
-	srvConf, err := runtime.NewServerConfiguration(conf, httpConf, log.WithContext(nil))
+	srvConf, err := runtime.NewServerConfiguration(conf, log.WithContext(nil))
 	helper.Must(err)
-	port := runtime.Port(httpConf.ListenPort)
 
-	couper := server.New(ctx, log.WithContext(ctx), httpConf, port, srvConf.PortOptions[port])
+	couper := server.New(ctx, log.WithContext(ctx), conf.Settings, &runtime.DefaultTimings, runtime.Port(0), srvConf[0])
 	couper.Listen()
 	defer couper.Close()
 
@@ -218,8 +213,9 @@ func TestHTTPServer_ServeHTTP_Files2(t *testing.T) {
 		//FIXME:
 		//{"/api", content500.Bytes(), 500},
 	} {
-		req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://example.com:%s%s", port, testCase.path), nil)
+		req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s%s", couper.Addr(), testCase.path), nil)
 		helper.Must(err)
+		req.Host = "example.com"
 
 		res, err := connectClient.Do(req)
 		helper.Must(err)
@@ -253,9 +249,9 @@ func TestHTTPServer_ServeHTTP_UUID_Option(t *testing.T) {
 	} {
 		t.Run(testcase.formatOption, func(subT *testing.T) {
 			log, hook := logrustest.NewNullLogger()
-			conf := *runtime.DefaultHTTP
-			conf.RequestIDFormat = testcase.formatOption
-			srv := server.New(context.Background(), log, &conf, 0, nil)
+			settings := config.DefaultSettings
+			settings.RequestIDFormat = testcase.formatOption
+			srv := server.New(context.Background(), log, &settings, &runtime.DefaultTimings, 0, nil)
 			srv.Listen()
 			defer srv.Close()
 
diff --git a/server/testdata/file_serving/conf_fileserving.hcl b/server/testdata/file_serving/conf_fileserving.hcl
index 0e5ceb78..d978919f 100644
--- a/server/testdata/file_serving/conf_fileserving.hcl
+++ b/server/testdata/file_serving/conf_fileserving.hcl
@@ -1,5 +1,5 @@
 server "fileserving-tests" {
-    hosts = ["example.com"]
+    hosts = ["example.com:0"]
 
     error_file = "./error.html"
 
@@ -27,7 +27,7 @@ server "fileserving-tests" {
             backend {
                 path = "/**"
                 origin = "{{.origin}}"
-                hostname = "{{.hostname}}"
+                hostname = "test.couper.io"
             }
         }
     }
diff --git a/server/testdata/integration/api/01_couper.hcl b/server/testdata/integration/api/01_couper.hcl
index ab9be1e3..b97c2106 100644
--- a/server/testdata/integration/api/01_couper.hcl
+++ b/server/testdata/integration/api/01_couper.hcl
@@ -12,6 +12,7 @@ server "api" {
 
     endpoint "/connect-error" {
       backend {
+        connect_timeout = "2s"
         origin = "http://1.2.3.4"
       }
     }
@@ -22,6 +23,6 @@ definitions {
   # backend origin within a definition block gets replaced with the integration test "anything" server.
   backend "anything" {
     path = "/anything"
-    origin = "http://anyserver/"
+    origin = env.COUPER_TEST_BACKEND_ADDR
   }
 }
diff --git a/server/testdata/integration/api/02_couper.hcl b/server/testdata/integration/api/02_couper.hcl
index cb64d528..61c43222 100644
--- a/server/testdata/integration/api/02_couper.hcl
+++ b/server/testdata/integration/api/02_couper.hcl
@@ -32,6 +32,6 @@ definitions {
   # backend origin within a definition block gets replaced with the integration test "anything" server.
   backend "anything" {
     path = "/anything"
-    origin = "http://anyserver/"
+    origin = env.COUPER_TEST_BACKEND_ADDR
   }
 }
diff --git a/server/testdata/integration/api/03_couper.hcl b/server/testdata/integration/api/03_couper.hcl
index c3be8a48..44787bb4 100644
--- a/server/testdata/integration/api/03_couper.hcl
+++ b/server/testdata/integration/api/03_couper.hcl
@@ -30,6 +30,6 @@ definitions {
   # backend origin within a definition block gets replaced with the integration test "anything" server.
   backend "anything" {
     path = "/anything"
-    origin = "http://anyserver/"
+    origin = env.COUPER_TEST_BACKEND_ADDR
   }
 }
diff --git a/server/testdata/integration/endpoint_eval/01_couper.hcl b/server/testdata/integration/endpoint_eval/01_couper.hcl
index f80b6841..2f5efac8 100644
--- a/server/testdata/integration/endpoint_eval/01_couper.hcl
+++ b/server/testdata/integration/endpoint_eval/01_couper.hcl
@@ -26,6 +26,6 @@ definitions {
   # backend origin within a definition block gets replaced with the integration test "anything" server.
   backend "anything" {
     path = "/not-found-anything"
-    origin = "http://anyserver/"
+    origin = env.COUPER_TEST_BACKEND_ADDR
   }
 }
diff --git a/server/testdata/integration/endpoint_eval/02_couper.hcl b/server/testdata/integration/endpoint_eval/02_couper.hcl
index 2c89d0f3..60be67fd 100644
--- a/server/testdata/integration/endpoint_eval/02_couper.hcl
+++ b/server/testdata/integration/endpoint_eval/02_couper.hcl
@@ -31,6 +31,6 @@ definitions {
   # backend origin within a definition block gets replaced with the integration test "anything" server.
   backend "anything" {
     path = "/unset/by/endpoint"
-    origin = "http://anyserver/"
+    origin = env.COUPER_TEST_BACKEND_ADDR
   }
 }
diff --git a/server/testdata/integration/endpoint_eval/03_couper.hcl b/server/testdata/integration/endpoint_eval/03_couper.hcl
index 517aaebe..f721f356 100644
--- a/server/testdata/integration/endpoint_eval/03_couper.hcl
+++ b/server/testdata/integration/endpoint_eval/03_couper.hcl
@@ -33,6 +33,6 @@ definitions {
   # backend origin within a definition block gets replaced with the integration test "anything" server.
   backend "anything" {
     path = "/unset/by/endpoint"
-    origin = "http://anyserver/"
+    origin = env.COUPER_TEST_BACKEND_ADDR
   }
 }
diff --git a/server/testdata/integration/endpoint_eval/04_couper.hcl b/server/testdata/integration/endpoint_eval/04_couper.hcl
index daf1f523..ebb7775a 100644
--- a/server/testdata/integration/endpoint_eval/04_couper.hcl
+++ b/server/testdata/integration/endpoint_eval/04_couper.hcl
@@ -91,13 +91,13 @@ server "api" {
 #       fe_null = null
 #       fe_empty = ""
 #     }
-#   } 
+#   }
 }
 
 definitions {
   # backend origin within a definition block gets replaced with the integration test "anything" server.
   backend "anything" {
-    origin = "http://anyserver/"
+    origin = env.COUPER_TEST_BACKEND_ADDR
 
     remove_query_params = [ "def_del" ]
     set_query_params = {
diff --git a/server/testdata/integration/endpoint_eval/05_couper.hcl b/server/testdata/integration/endpoint_eval/05_couper.hcl
index 2d8368a0..43ed025f 100644
--- a/server/testdata/integration/endpoint_eval/05_couper.hcl
+++ b/server/testdata/integration/endpoint_eval/05_couper.hcl
@@ -15,7 +15,7 @@ definitions {
   # backend origin within a definition block gets replaced with the integration test "anything" server.
   backend "anything" {
     path = "/xxx"
-    origin = "http://anyserver/"
+    origin = env.COUPER_TEST_BACKEND_ADDR
 
     set_query_params = {
       def = "def"
diff --git a/server/testdata/integration/endpoint_eval/06_couper.hcl b/server/testdata/integration/endpoint_eval/06_couper.hcl
index e3d1a08e..a17d3dab 100644
--- a/server/testdata/integration/endpoint_eval/06_couper.hcl
+++ b/server/testdata/integration/endpoint_eval/06_couper.hcl
@@ -17,7 +17,7 @@ definitions {
   # backend origin within a definition block gets replaced with the integration test "anything" server.
   backend "anything" {
     path = "/xxx"
-    origin = "http://anyserver/"
+    origin = env.COUPER_TEST_BACKEND_ADDR
 
     add_query_params = {
       def = "def"
diff --git a/server/testdata/integration/endpoint_eval/07_couper.hcl b/server/testdata/integration/endpoint_eval/07_couper.hcl
index 12e74f45..c6cf6610 100644
--- a/server/testdata/integration/endpoint_eval/07_couper.hcl
+++ b/server/testdata/integration/endpoint_eval/07_couper.hcl
@@ -14,7 +14,7 @@ definitions {
   # backend origin within a definition block gets replaced with the integration test "anything" server.
   backend "anything" {
     path = "/xxx"
-    origin = "http://anyserver/"
+    origin = env.COUPER_TEST_BACKEND_ADDR
 
     add_query_params = {
       def = "def"
diff --git a/server/testdata/integration/endpoint_eval/08_couper.hcl b/server/testdata/integration/endpoint_eval/08_couper.hcl
index 166aca4f..75a93862 100644
--- a/server/testdata/integration/endpoint_eval/08_couper.hcl
+++ b/server/testdata/integration/endpoint_eval/08_couper.hcl
@@ -32,7 +32,7 @@ definitions {
   # backend origin within a definition block gets replaced with the integration test "anything" server.
   backend "anything" {
     path = "/not-found-anything"
-    origin = "http://anyserver/"
+    origin = env.COUPER_TEST_BACKEND_ADDR
     openapi {
       file = "not-there.yml"
     }
diff --git a/server/testdata/integration/files_spa_api/01_couper.hcl b/server/testdata/integration/files_spa_api/01_couper.hcl
index 0090f8a1..fbaea26d 100644
--- a/server/testdata/integration/files_spa_api/01_couper.hcl
+++ b/server/testdata/integration/files_spa_api/01_couper.hcl
@@ -20,6 +20,6 @@ definitions {
   # backend origin within a definition block gets replaced with the integration test "anything" server.
   backend "anything" {
     path = "/anything"
-    origin = "http://anyserver/"
+    origin = env.COUPER_TEST_BACKEND_ADDR
   }
 }
diff --git a/server/testdata/integration/vhosts/01_couper.hcl b/server/testdata/integration/vhosts/01_couper.hcl
index 3969961c..8aaf1b3f 100644
--- a/server/testdata/integration/vhosts/01_couper.hcl
+++ b/server/testdata/integration/vhosts/01_couper.hcl
@@ -60,6 +60,6 @@ definitions {
   # backend origin within a definition block gets replaced with the integration test "anything" server.
   backend "anything" {
     path = "/anything"
-    origin = "http://anyserver/"
+    origin = env.COUPER_TEST_BACKEND_ADDR
   }
 }