Repository: avenga/couper
Pull request #433 (2022-02-01 17:20:24)

Singleton backends
This will change the internal backend configuration. Now a defined backend will be an instance with configured transport settings like upstream addr, certificate and connection options. Those settings can't be refined for a reference.
This will fix an issue where a refined backend runs with its own connection pool which would make the `max_connections` attribute obsolete. Additionally the blocked #339 backend-health option will work as intended.

Blocked attributes are:
`disable_certificate_validation`, `disable_connection_reuse`, `http2`, `max_connections`, `openapi`
and the `origin` value expression must be the same if provided.

---
<details>
    <summary>Reviewer checklist</summary>
    <ul>
        <li>Read PR description: a summary about the changes is required</li>
        <li>Changelog updated</li>
        <li>Documentation: docs/{Reference, Cli, ...}, Docker and cli help/usage</li>
        <li>Pulled branch, manually tested</li>
        <li>Verified requirements are met</li>
        <li>Reviewed the code</li>
        <li>Reviewed the related tests</li>
    </ul>
</details>



Patch:
diff --git a/Makefile b/Makefile
index 7b3eb14a..c88bab43 100644
--- a/Makefile
+++ b/Makefile
@@ -18,7 +18,7 @@ test:
 	go test -v -short -race -count 1 -timeout 300s ./...
 
 test-docker:
-	docker run --rm -v $(CURDIR):/go/app -w /go/app  golang sh -c "go test -short -count 10 -v -timeout 300s -race ./..."
+	docker run --rm -v $(CURDIR):/go/app -w /go/app  golang sh -c "go test -short -count 1 -v -timeout 300s -race ./..."
 
 coverage: test-coverage test-coverage-show
 
diff --git a/accesscontrol/jwk/jwk.go b/accesscontrol/jwk/jwk.go
index 13aa4a6e..6f592dc9 100644
--- a/accesscontrol/jwk/jwk.go
+++ b/accesscontrol/jwk/jwk.go
@@ -39,8 +39,8 @@ type rawJWK struct {
 }
 
 func (j *JWK) UnmarshalJSON(data []byte) error {
-	var raw rawJWK
-	err := json.Unmarshal(data, &raw)
+	raw := &rawJWK{}
+	err := json.Unmarshal(data, raw)
 	if err != nil {
 		// TODO log warning properly
 		fmt.Printf("Invalid JWK: %v\n", err)
@@ -48,7 +48,7 @@ func (j *JWK) UnmarshalJSON(data []byte) error {
 	}
 
 	var key interface{}
-	jwk := JWK{KeyID: raw.Kid, Algorithm: raw.Alg, KeyType: raw.Kty, Use: raw.Use}
+	jwk := &JWK{KeyID: raw.Kid, Algorithm: raw.Alg, KeyType: raw.Kty, Use: raw.Use}
 
 	switch raw.Kty {
 	case "RSA":
@@ -100,7 +100,7 @@ func (j *JWK) UnmarshalJSON(data []byte) error {
 		return nil
 	}
 
-	*j = jwk
+	*j = *jwk
 
 	return nil
 }
diff --git a/accesscontrol/jwk/jwks.go b/accesscontrol/jwk/jwks.go
index 4cb2b366..abeaec90 100644
--- a/accesscontrol/jwk/jwks.go
+++ b/accesscontrol/jwk/jwks.go
@@ -1,7 +1,6 @@
 package jwk
 
 import (
-	"context"
 	"encoding/json"
 	"fmt"
 	"net/http"
@@ -23,14 +22,14 @@ var alg2kty = map[string]string{
 }
 
 type JWKSData struct {
-	Keys []JWK `json:"keys"`
+	Keys []*JWK `json:"keys"`
 }
 
 type JWKS struct {
 	syncedJSON *jsn.SyncedJSON
 }
 
-func NewJWKS(uri string, ttl string, transport http.RoundTripper, confContext context.Context) (*JWKS, error) {
+func NewJWKS(uri string, ttl string, transport http.RoundTripper) (*JWKS, error) {
 	if ttl == "" {
 		ttl = "1h"
 	}
@@ -47,9 +46,14 @@ func NewJWKS(uri string, ttl string, transport http.RoundTripper, confContext co
 	}
 
 	jwks := &JWKS{}
-	sj := jsn.NewSyncedJSON(confContext, file, "jwks_url", uri, transport, "jwks" /* TODO which roundtrip name? */, timetolive, jwks)
-	jwks.syncedJSON = sj
-	return jwks, nil
+	jwks.syncedJSON, err = jsn.NewSyncedJSON(file, "jwks_url", uri, transport, "jwks", timetolive, jwks)
+	if err != nil {
+		return nil, err
+	}
+
+	_, err = jwks.Data("") // obtain any initial fetch errors
+
+	return jwks, err
 }
 
 func (j *JWKS) GetSigKeyForToken(token *jwt.Token) (interface{}, error) {
@@ -73,36 +77,19 @@ func (j *JWKS) GetSigKeyForToken(token *jwt.Token) (interface{}, error) {
 	return jwk.Key, nil
 }
 
-func (j *JWKS) GetKeys(kid string) ([]JWK, error) {
-	var keys []JWK
-
-	jwksData, err := j.Data("")
-	if err != nil {
-		return nil, err
-	}
-
-	allKeys := jwksData.Keys
-	for _, key := range allKeys {
-		if key.KeyID == kid {
-			keys = append(keys, key)
-		}
-	}
-
-	return keys, nil
-}
-
 func (j *JWKS) GetKey(kid string, alg string, use string) (*JWK, error) {
-	keys, err := j.GetKeys(kid)
+	keys, err := j.getKeys(kid)
 	if err != nil {
 		return nil, err
 	}
+
 	for _, key := range keys {
 		if key.Use == use {
 			if key.Algorithm == alg {
-				return &key, nil
+				return key, nil
 			} else if key.Algorithm == "" {
 				if kty, exists := alg2kty[alg]; exists && key.KeyType == kty {
-					return &key, nil
+					return key, nil
 				}
 			}
 		}
@@ -110,25 +97,43 @@ func (j *JWKS) GetKey(kid string, alg string, use string) (*JWK, error) {
 	return nil, nil
 }
 
+func (j *JWKS) getKeys(kid string) ([]*JWK, error) {
+	var keys []*JWK
+
+	jwksData, err := j.Data("")
+	if err != nil {
+		return nil, err
+	}
+
+	if len(jwksData.Keys) == 0 {
+		return nil, fmt.Errorf("missing jwks key-data")
+	}
+
+	for _, key := range jwksData.Keys {
+		if key.KeyID == kid {
+			keys = append(keys, key)
+		}
+	}
+
+	return keys, nil
+}
+
 func (j *JWKS) Data(uid string) (*JWKSData, error) {
 	data, err := j.syncedJSON.Data(uid)
 	if err != nil {
 		return nil, err
 	}
 
-	jwksData, ok := data.(JWKSData)
+	jwksData, ok := data.(*JWKSData)
 	if !ok {
 		return nil, fmt.Errorf("data not JWKS data: %#v", data)
 	}
 
-	return &jwksData, nil
+	return jwksData, nil
 }
 
-func (j *JWKS) Unmarshal(rawJSON []byte, _ string) (interface{}, error) {
-	var jsonData JWKSData
-	err := json.Unmarshal(rawJSON, &jsonData)
-	if err != nil {
-		return nil, err
-	}
-	return jsonData, nil
+func (j *JWKS) Unmarshal(rawJSON []byte) (interface{}, error) {
+	jsonData := &JWKSData{}
+	err := json.Unmarshal(rawJSON, jsonData)
+	return jsonData, err
 }
diff --git a/accesscontrol/jwt.go b/accesscontrol/jwt.go
index d704891c..2d73dce7 100644
--- a/accesscontrol/jwt.go
+++ b/accesscontrol/jwt.go
@@ -270,27 +270,6 @@ func (j *JWT) Validate(req *http.Request) error {
 	return nil
 }
 
-func GetKeyFromJWKS(jwks *jwk.JWKS, token *jwt.Token) (interface{}, error) {
-	id := token.Header["kid"]
-	algorithm := token.Header["alg"]
-	if id == nil {
-		return nil, fmt.Errorf("missing \"kid\" in JOSE header")
-	}
-	if algorithm == nil {
-		return nil, fmt.Errorf("missing \"alg\" in JOSE header")
-	}
-	jwk, err := jwks.GetKey(id.(string), algorithm.(string), "sig")
-	if err != nil {
-		return nil, err
-	}
-
-	if jwk == nil {
-		return nil, fmt.Errorf("no matching %s JWK for kid %q", algorithm, id)
-	}
-
-	return jwk.Key, nil
-}
-
 func (j *JWT) getValidationKey(token *jwt.Token) (interface{}, error) {
 	if j.jwks != nil {
 		return j.jwks.GetSigKeyForToken(token)
diff --git a/accesscontrol/oauth2.go b/accesscontrol/oauth2.go
index 97d839c4..9a6444a0 100644
--- a/accesscontrol/oauth2.go
+++ b/accesscontrol/oauth2.go
@@ -13,11 +13,11 @@ var _ AccessControl = &OAuth2Callback{}
 
 // OAuth2Callback represents the access control for the OAuth2 authorization code flow callback.
 type OAuth2Callback struct {
-	oauth2Client oauth2.AcClient
+	oauth2Client oauth2.AuthCodeFlowClient
 }
 
 // NewOAuth2Callback creates a new access control for the OAuth2 authorization code flow callback.
-func NewOAuth2Callback(oauth2Client oauth2.AcClient) (*OAuth2Callback, error) {
+func NewOAuth2Callback(oauth2Client oauth2.AuthCodeFlowClient) (*OAuth2Callback, error) {
 	return &OAuth2Callback{
 		oauth2Client: oauth2Client,
 	}, nil
diff --git a/backend/backend_context.go b/backend/backend_context.go
new file mode 100644
index 00000000..6fa9250c
--- /dev/null
+++ b/backend/backend_context.go
@@ -0,0 +1,28 @@
+package backend
+
+import (
+	"context"
+	"net/http"
+
+	"github.com/avenga/couper/config/request"
+	"github.com/hashicorp/hcl/v2"
+)
+
+var _ http.RoundTripper = &Context{}
+
+type Context struct {
+	body hcl.Body
+	rt   http.RoundTripper
+}
+
+func NewContext(body hcl.Body, rt http.RoundTripper) http.RoundTripper {
+	return &Context{
+		body: body,
+		rt:   rt,
+	}
+}
+
+func (b *Context) RoundTrip(req *http.Request) (*http.Response, error) {
+	ctx := context.WithValue(req.Context(), request.BackendParams, b.body)
+	return b.rt.RoundTrip(req.WithContext(ctx))
+}
diff --git a/command/verify.go b/command/verify.go
index 024db276..0ce8b6fc 100644
--- a/command/verify.go
+++ b/command/verify.go
@@ -43,6 +43,8 @@ func (v Verify) Execute(args Args, _ *config.Couper, logger *logrus.Entry) error
 	}
 
 	tmpStoreCh := make(chan struct{})
+	defer close(tmpStoreCh)
+
 	tmpMemStore := cache.New(logger, tmpStoreCh)
 
 	_, err = runtime.NewServerConfiguration(cf, logger, tmpMemStore)
diff --git a/config/ac_jwt.go b/config/ac_jwt.go
index ec805b62..2316daf5 100644
--- a/config/ac_jwt.go
+++ b/config/ac_jwt.go
@@ -1,12 +1,14 @@
 package config
 
 import (
-	"errors"
-
+	"fmt"
 	"github.com/hashicorp/hcl/v2"
 	"github.com/hashicorp/hcl/v2/gohcl"
+
+	"github.com/avenga/couper/errors"
 )
 
+var _ BackendInitialization = &JWT{}
 var _ Inline = &JWT{}
 
 // Claims represents the <Claims> object.
@@ -39,7 +41,24 @@ type JWT struct {
 
 	// Internally used
 	BodyContent *hcl.BodyContent
-	Backend     hcl.Body
+	Backends    map[string]hcl.Body
+}
+
+func (j *JWT) Prepare(backendFunc PrepareBackendFunc) (err error) {
+	if j.JWKsURL != "" {
+		if j.Backends == nil {
+			j.Backends = make(map[string]hcl.Body)
+		}
+		j.Backends["backend"], err = backendFunc("jwks_url", j.JWKsURL, j)
+		if err != nil {
+			return err
+		}
+	}
+
+	if err = j.check(); err != nil {
+		return errors.Configuration.Label(j.Name).With(err)
+	}
+	return nil
 }
 
 // Reference implements the <BackendReference> interface.
@@ -79,9 +98,15 @@ func (j *JWT) Schema(inline bool) *hcl.BodySchema {
 	return newBackendSchema(schema, j.HCLBody())
 }
 
-func (j *JWT) Check() error {
-	if j.BackendName != "" && j.Backend != nil {
-		return errors.New("backend must be either block or attribute")
+func (j *JWT) check() error {
+	if j.JWKsURL == "" && j.SignatureAlgorithm == "" {
+		return fmt.Errorf("signature_algorithm or jwks_url attribute required")
+	}
+
+	if j.JWKsURL == "" && (j.BackendName != "" || j.Backends != nil) {
+		return fmt.Errorf("backend is obsolete without jwks_url attribute")
+	} else if j.BackendName != "" && j.Backends == nil {
+		return fmt.Errorf("backend must be either a block or an attribute")
 	}
 
 	if j.JWKsURL != "" {
@@ -93,17 +118,9 @@ func (j *JWT) Check() error {
 
 		for name, value := range attributes {
 			if value != "" {
-				return errors.New(name + " cannot be used together with jwks_url")
+				return fmt.Errorf("%s cannot be used together with jwks_url", name)
 			}
 		}
-	} else {
-		if j.BackendName != "" || j.Backend != nil {
-			return errors.New("backend not needed without jwks_url")
-		}
-
-		if j.SignatureAlgorithm == "" {
-			return errors.New("signature_algorithm or jwks_url required")
-		}
 	}
 
 	return nil
diff --git a/config/ac_oauth2.go b/config/ac_oauth2.go
index 2c277178..ff2f638c 100644
--- a/config/ac_oauth2.go
+++ b/config/ac_oauth2.go
@@ -6,17 +6,18 @@ import (
 )
 
 var (
-	_ BackendReference    = &OAuth2AC{}
-	_ Inline              = &OAuth2AC{}
-	_ OAuth2AcClient      = &OAuth2AC{}
-	_ OAuth2AcAS          = &OAuth2AC{}
-	_ OAuth2Authorization = &OAuth2AC{}
+	_ BackendReference      = &OAuth2AC{}
+	_ BackendInitialization = &OAuth2AC{}
+	_ Inline                = &OAuth2AC{}
+	_ OAuth2AcAS            = &OAuth2AC{}
+	_ OAuth2AcClient        = &OAuth2AC{}
+	_ OAuth2Authorization   = &OAuth2AC{}
 )
 
 // OAuth2AC represents an oauth2 block for an OAuth2 client using the authorization code flow.
 type OAuth2AC struct {
 	ErrorHandlerSetter
-	AuthorizationEndpoint   string   `hcl:"authorization_endpoint"`
+	AuthorizationEndpoint   string   `hcl:"authorization_endpoint"` // used for lib.FnOAuthAuthorizationUrl
 	BackendName             string   `hcl:"backend,optional"`
 	ClientID                string   `hcl:"client_id"`
 	ClientSecret            string   `hcl:"client_secret"`
@@ -29,22 +30,26 @@ type OAuth2AC struct {
 	VerifierMethod          string   `hcl:"verifier_method"`
 
 	// internally used
-	Backend     hcl.Body
-	BodyContent *hcl.BodyContent
+	Backend hcl.Body
+}
+
+func (oa *OAuth2AC) Prepare(backendFunc PrepareBackendFunc) (err error) {
+	oa.Backend, err = backendFunc("token_endpoint", oa.TokenEndpoint, oa)
+	return err
 }
 
 // Reference implements the <BackendReference> interface.
-func (oa OAuth2AC) Reference() string {
+func (oa *OAuth2AC) Reference() string {
 	return oa.BackendName
 }
 
 // HCLBody implements the <Inline> interface.
-func (oa OAuth2AC) HCLBody() hcl.Body {
+func (oa *OAuth2AC) HCLBody() hcl.Body {
 	return oa.Remain
 }
 
 // Inline implements the <Inline> interface.
-func (oa OAuth2AC) Inline() interface{} {
+func (oa *OAuth2AC) Inline() interface{} {
 	type Inline struct {
 		Backend       *Backend                  `hcl:"backend,block"`
 		LogFields     map[string]hcl.Expression `hcl:"custom_log_fields,optional"`
@@ -56,7 +61,7 @@ func (oa OAuth2AC) Inline() interface{} {
 }
 
 // Schema implements the <Inline> interface.
-func (oa OAuth2AC) Schema(inline bool) *hcl.BodySchema {
+func (oa *OAuth2AC) Schema(inline bool) *hcl.BodySchema {
 	if !inline {
 		schema, _ := gohcl.ImpliedBodySchema(oa)
 		return schema
@@ -72,46 +77,42 @@ func (oa OAuth2AC) Schema(inline bool) *hcl.BodySchema {
 	return newBackendSchema(schema, oa.HCLBody())
 }
 
-func (oa *OAuth2AC) GetBodyContent() *hcl.BodyContent {
-	return oa.BodyContent
-}
-
-func (oa OAuth2AC) GetName() string {
+func (oa *OAuth2AC) GetName() string {
 	return oa.Name
 }
 
-func (oa OAuth2AC) GetClientID() string {
+func (oa *OAuth2AC) GetClientID() string {
 	return oa.ClientID
 }
 
-func (oa OAuth2AC) GetClientSecret() string {
+func (oa *OAuth2AC) GetClientSecret() string {
 	return oa.ClientSecret
 }
 
-func (oa OAuth2AC) GetGrantType() string {
+func (oa *OAuth2AC) GetGrantType() string {
 	return oa.GrantType
 }
 
-func (oa OAuth2AC) GetScope() string {
+func (oa *OAuth2AC) GetScope() string {
 	if oa.Scope == nil {
 		return ""
 	}
 	return *oa.Scope
 }
 
-func (oa OAuth2AC) GetAuthorizationEndpoint(_ string) (string, error) {
+func (oa *OAuth2AC) GetAuthorizationEndpoint(_ string) (string, error) {
 	return oa.AuthorizationEndpoint, nil
 }
 
-func (oa OAuth2AC) GetTokenEndpoint() (string, error) {
+func (oa *OAuth2AC) GetTokenEndpoint() (string, error) {
 	return oa.TokenEndpoint, nil
 }
 
-func (oa OAuth2AC) GetTokenEndpointAuthMethod() *string {
+func (oa *OAuth2AC) GetTokenEndpointAuthMethod() *string {
 	return oa.TokenEndpointAuthMethod
 }
 
 // GetVerifierMethod retrieves the verifier method (ccm_s256 or state)
-func (oa OAuth2AC) GetVerifierMethod(_ string) (string, error) {
+func (oa *OAuth2AC) GetVerifierMethod(_ string) (string, error) {
 	return oa.VerifierMethod, nil
 }
diff --git a/config/ac_oidc.go b/config/ac_oidc.go
index e59b8ed8..ab74d01d 100644
--- a/config/ac_oidc.go
+++ b/config/ac_oidc.go
@@ -3,14 +3,19 @@ package config
 import (
 	"github.com/hashicorp/hcl/v2"
 	"github.com/hashicorp/hcl/v2/gohcl"
+
+	hclbody "github.com/avenga/couper/config/body"
 )
 
 var (
-	_ BackendReference = &OIDC{}
-	_ Inline           = &OIDC{}
+	_ BackendReference      = &OIDC{}
+	_ BackendInitialization = &OIDC{}
+	_ Inline                = &OIDC{}
 )
 
-// OIDC represents an oidc block.
+// OIDC represents an oidc block. The backend block will be used as backend template for all
+// configuration related backends. Backend references along with an anonymous one must match
+// the url with the backend origin definition.
 type OIDC struct {
 	ErrorHandlerSetter
 	BackendName             string   `hcl:"backend,optional"`
@@ -24,35 +29,68 @@ type OIDC struct {
 	ConfigurationTTL        string   `hcl:"configuration_ttl,optional"`
 	VerifierMethod          string   `hcl:"verifier_method,optional"`
 
+	// configuration related backends
+	ConfigurationBackendName string `hcl:"configuration_backend,optional"`
+	JWKSBackendName          string `hcl:"jwks_uri_backend,optional"`
+	TokenBackendName         string `hcl:"token_backend,optional"`
+	UserinfoBackendName      string `hcl:"userinfo_backend,optional"`
+
 	// internally used
-	Backend     hcl.Body
-	BodyContent *hcl.BodyContent
+	Backends map[string]hcl.Body
+}
+
+func (o *OIDC) Prepare(backendFunc PrepareBackendFunc) (err error) {
+	if o.Backends == nil {
+		o.Backends = make(map[string]hcl.Body)
+	}
+
+	fields := BackendAttrFields(o)
+	for _, field := range fields {
+		fieldValue := AttrValueFromTagField(field, o)
+		o.Backends[field], err = backendFunc(field, fieldValue, o)
+		if err != nil {
+			return err
+		}
+
+		// exceptions
+		o.Backends[field] = hclbody.MergeBodies(o.Backends[field],
+			hclbody.New(hclbody.NewContentWithAttrName("_oidc_backend", "true")))
+	}
+	return nil
 }
 
 // Reference implements the <BackendReference> interface.
-func (o OIDC) Reference() string {
+func (o *OIDC) Reference() string {
 	return o.BackendName
 }
 
 // HCLBody implements the <Inline> interface.
-func (o OIDC) HCLBody() hcl.Body {
+func (o *OIDC) HCLBody() hcl.Body {
 	return o.Remain
 }
 
 // Inline implements the <Inline> interface.
-func (o OIDC) Inline() interface{} {
+func (o *OIDC) Inline() interface{} {
 	type Inline struct {
 		Backend       *Backend                  `hcl:"backend,block"`
 		LogFields     map[string]hcl.Expression `hcl:"custom_log_fields,optional"`
 		RedirectURI   string                    `hcl:"redirect_uri"`
 		VerifierValue string                    `hcl:"verifier_value"`
+
+		AuthorizationBackend       *Backend `hcl:"authorization_backend,block"`
+		ConfigurationBackend       *Backend `hcl:"configuration_backend,block"`
+		DeviceAuthorizationBackend *Backend `hcl:"device_authorization_backend,block"`
+		JWKSBackend                *Backend `hcl:"jwks_uri_backend,block"`
+		RevocationBackend          *Backend `hcl:"revocation_backend,block"`
+		TokenBackend               *Backend `hcl:"token_backend,block"`
+		UserinfoBackend            *Backend `hcl:"userinfo_backend,block"`
 	}
 
 	return &Inline{}
 }
 
 // Schema implements the <Inline> interface.
-func (o OIDC) Schema(inline bool) *hcl.BodySchema {
+func (o *OIDC) Schema(inline bool) *hcl.BodySchema {
 	if !inline {
 		schema, _ := gohcl.ImpliedBodySchema(o)
 		return schema
@@ -68,33 +106,29 @@ func (o OIDC) Schema(inline bool) *hcl.BodySchema {
 	return newBackendSchema(schema, o.HCLBody())
 }
 
-func (o *OIDC) GetBodyContent() *hcl.BodyContent {
-	return o.BodyContent
-}
-
-func (o OIDC) GetName() string {
+func (o *OIDC) GetName() string {
 	return o.Name
 }
 
-func (o OIDC) GetClientID() string {
+func (o *OIDC) GetClientID() string {
 	return o.ClientID
 }
 
-func (o OIDC) GetClientSecret() string {
+func (o *OIDC) GetClientSecret() string {
 	return o.ClientSecret
 }
 
-func (o OIDC) GetGrantType() string {
+func (o *OIDC) GetGrantType() string {
 	return "authorization_code"
 }
 
-func (o OIDC) GetScope() string {
+func (o *OIDC) GetScope() string {
 	if o.Scope == nil {
 		return "openid"
 	}
 	return "openid " + *o.Scope
 }
 
-func (o OIDC) GetTokenEndpointAuthMethod() *string {
+func (o *OIDC) GetTokenEndpointAuthMethod() *string {
 	return o.TokenEndpointAuthMethod
 }
diff --git a/config/backend.go b/config/backend.go
index 11eedfb3..912323b8 100644
--- a/config/backend.go
+++ b/config/backend.go
@@ -16,7 +16,6 @@ var (
 
 // Backend represents the <Backend> object.
 type Backend struct {
-	ConnectTimeout         string   `hcl:"connect_timeout,optional"`
 	DisableCertValidation  bool     `hcl:"disable_certificate_validation,optional"`
 	DisableConnectionReuse bool     `hcl:"disable_connection_reuse,optional"`
 	HTTP2                  bool     `hcl:"http2,optional"`
@@ -24,8 +23,6 @@ type Backend struct {
 	Name                   string   `hcl:"name,label"`
 	OpenAPI                *OpenAPI `hcl:"openapi,block"`
 	Remain                 hcl.Body `hcl:",remain"`
-	TTFBTimeout            string   `hcl:"ttfb_timeout,optional"`
-	Timeout                string   `hcl:"timeout,optional"`
 
 	// explicit configuration on load
 	OAuth2 *OAuth2ReqAuth
@@ -46,12 +43,18 @@ func (b Backend) Inline() interface{} {
 	type Inline struct {
 		meta.Attributes
 		BasicAuth      string                    `hcl:"basic_auth,optional"`
+		ConnectTimeout string                    `hcl:"connect_timeout,optional"`
 		Hostname       string                    `hcl:"hostname,optional"`
 		LogFields      map[string]hcl.Expression `hcl:"custom_log_fields,optional"`
 		Origin         string                    `hcl:"origin,optional"`
 		PathPrefix     string                    `hcl:"path_prefix,optional"`
 		ProxyURL       string                    `hcl:"proxy,optional"`
 		ResponseStatus *uint8                    `hcl:"set_response_status,optional"`
+		TTFBTimeout    string                    `hcl:"ttfb_timeout,optional"`
+		Timeout        string                    `hcl:"timeout,optional"`
+
+		// set by backend preparation
+		BackendURL string `hcl:"backend_url,optional"`
 	}
 
 	return &Inline{}
diff --git a/config/body/body.go b/config/body/body.go
index f19b22ad..bc882916 100644
--- a/config/body/body.go
+++ b/config/body/body.go
@@ -1,6 +1,10 @@
 package body
 
-import "github.com/hashicorp/hcl/v2"
+import (
+	"github.com/hashicorp/hcl/v2"
+	"github.com/hashicorp/hcl/v2/hclsyntax"
+	"github.com/zclconf/go-cty/cty"
+)
 
 var _ hcl.Body = &Body{}
 
@@ -17,6 +21,19 @@ func New(content *hcl.BodyContent) hcl.Body {
 	return &Body{content}
 }
 
+func NewContentWithAttrName(name, value string) *hcl.BodyContent {
+	return NewContentWithAttr(&hcl.Attribute{
+		Name: name,
+		Expr: &hclsyntax.LiteralValueExpr{Val: cty.StringVal(value)},
+	})
+}
+
+func NewContentWithAttr(attr *hcl.Attribute) *hcl.BodyContent {
+	return &hcl.BodyContent{Attributes: map[string]*hcl.Attribute{
+		attr.Name: attr,
+	}}
+}
+
 func (e *Body) Content(_ *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
 	return e.content, nil
 }
diff --git a/config/body/merged.go b/config/body/merged.go
index 28977f19..6c5db8c2 100644
--- a/config/body/merged.go
+++ b/config/body/merged.go
@@ -7,6 +7,7 @@ import (
 	"fmt"
 
 	"github.com/hashicorp/hcl/v2"
+	"github.com/hashicorp/hcl/v2/hclsyntax"
 )
 
 // reportDuplicates is the global switch to handle force merges.
@@ -114,8 +115,14 @@ func (mb MergedBodies) MissingItemRange() hcl.Range {
 	}
 
 	for _, b := range mb {
-		if !b.MissingItemRange().Empty() {
-			return b.MissingItemRange()
+		if be, ok := b.(*hclsyntax.Body); ok {
+			if !be.SrcRange.Empty() {
+				return be.SrcRange
+			}
+		} else {
+			if !b.MissingItemRange().Empty() {
+				return b.MissingItemRange()
+			}
 		}
 	}
 
diff --git a/config/configload/backend.go b/config/configload/backend.go
index 7728319a..1a053c8b 100644
--- a/config/configload/backend.go
+++ b/config/configload/backend.go
@@ -2,36 +2,17 @@ package configload
 
 import (
 	"fmt"
+	"strings"
 
 	"github.com/hashicorp/hcl/v2"
+	"github.com/hashicorp/hcl/v2/gohcl"
 	"github.com/hashicorp/hcl/v2/hclsyntax"
 	"github.com/zclconf/go-cty/cty"
 
 	"github.com/avenga/couper/config"
 	hclbody "github.com/avenga/couper/config/body"
-	"github.com/avenga/couper/internal/seetie"
 )
 
-// Backends is a sequence of Backend
-type Backends []*Backend
-
-type Backend struct {
-	attr   *hclsyntax.Attribute
-	Config hcl.Body
-	name   string
-}
-
-func NewBackend(name string, config hcl.Body) *Backend {
-	return &Backend{
-		attr: &hclsyntax.Attribute{
-			Name: "name",
-			Expr: &hclsyntax.LiteralValueExpr{Val: cty.StringVal(name)},
-		},
-		name:   name,
-		Config: config,
-	}
-}
-
 var backendBlockSchema = &hcl.BodySchema{
 	Blocks: []hcl.BlockHeaderSchema{
 		{
@@ -41,196 +22,206 @@ var backendBlockSchema = &hcl.BodySchema{
 	},
 }
 
-// WithName picks a matching backend configuration from the backend sequence and does
-// some attribute enrichment to be able to filter with this attributes later on.
-func (b Backends) WithName(name string) (hcl.Body, error) {
-	if len(b) == 0 || name == "" {
-		return nil, nil
+var defaultBackend = hclbody.New(&hcl.BodyContent{
+	Attributes: map[string]*hcl.Attribute{
+		"connect_timeout": {
+			Name: "connect_timeout",
+			Expr: &hclsyntax.LiteralValueExpr{Val: cty.StringVal("10s")},
+		},
+		"ttfb_timeout": {
+			Name: "ttfb_timeout",
+			Expr: &hclsyntax.LiteralValueExpr{Val: cty.StringVal("60s")},
+		},
+		"timeout": {
+			Name: "timeout",
+			Expr: &hclsyntax.LiteralValueExpr{Val: cty.StringVal("300s")},
+		},
+	},
+})
+
+// PrepareBackend is a method which is mandatory to call for preparing any kind of backend.
+// This applies to defined, reference, anonymous and endpoint/url related configurations.
+// This method will be called recursively and is used as wrapped injector for
+// access-control backends via config.PrepareBackendFunc.
+func PrepareBackend(helper *helper, attrName, attrValue string, block config.Inline) (hcl.Body, error) {
+	var reference string // backend definitions
+	var backendBody hcl.Body
+	var err error
+
+	reference, backendBody, err = getBackendReference(block)
+	if err != nil {
+		return nil, err
 	}
 
-	for _, item := range b {
-		if item.name == name {
-			if syntaxBody, ok := item.Config.(*hclsyntax.Body); ok {
-				if _, ok = syntaxBody.Attributes["name"]; ok {
-					return item.Config, nil
-				}
-				// explicit set on every call since this could be affected by user content.
-				// TODO: internal obj with hidden attributes
-				syntaxBody.Attributes["name"] = item.attr
-			}
-			return item.Config, nil
+	if reference != "" {
+		if strings.HasSuffix(attrName, "_backend") && attrValue != "" { // specific attribute overrides; prefer
+			reference = attrValue
 		}
-	}
 
-	return nil, hcl.Diagnostics{&hcl.Diagnostic{
-		Severity: hcl.DiagError,
-		Summary:  "backend reference is not defined: " + name,
-	}}
-}
-
-// mergeBackendBodies appends the left side object with newly defined attributes or overrides already defined ones.
-func mergeBackendBodies(definedBackends Backends, inline config.Inline) (hcl.Body, error) {
-	var reference hcl.Body
-	if beRef, ok := inline.(config.BackendReference); ok {
-		r, err := getBackendReference(definedBackends, beRef)
-		if err != nil {
-			return nil, err
+		refBody, ok := helper.defsBackends[reference]
+		if !ok {
+			r := block.HCLBody().MissingItemRange()
+			if backendBody != nil {
+				r = backendBody.MissingItemRange()
+			}
+			return nil, newDiagErr(&r, "backend reference is not defined: "+reference)
 		}
-		reference = r
-	}
 
-	content, _, diags := inline.HCLBody().PartialContent(inline.Schema(true))
-	if diags.HasErrors() {
-		return nil, diags
-	}
-
-	if content == nil {
-		if reference != nil {
-			return reference, nil
+		if backendBody == nil {
+			if attrName == "_init" { // initial definitions case
+				backendBody = hclbody.MergeBodies(defaultBackend, refBody)
+			} else { // plain reference without params
+				return refBody, nil
+			}
+		} else { // with backend params - do not repeat referenced hcl stack, just the name
+			if err = invalidRefinement(backendBody); err != nil {
+				return nil, err
+			}
+			if err = invalidOriginRefinement(refBody, backendBody); err != nil {
+				return nil, err
+			}
+			backendBody = hclbody.MergeBodies(
+				hclbody.New(hclbody.NewContentWithAttrName("name", reference)),
+				backendBody)
+		}
+	} else { // anonymous backend block
+		var labelRange *hcl.Range
+		var labelSuffix string
+		// anonymous backend based on a single attr, take the attr range instead
+		if attrName != "" && reference == "" {
+			labelRange, labelSuffix = refineAnonLabel(attrName, block.HCLBody())
 		}
-		return nil, fmt.Errorf("configuration error: missing backend reference or inline definition")
-	}
-
-	// Apply current attributes to the referenced body.
-	if len(content.Attributes) > 0 && reference != nil {
-		reference = hclbody.MergeBodies(reference, hclbody.New(&hcl.BodyContent{
-			Attributes:       content.Attributes,
-			MissingItemRange: content.MissingItemRange}),
-		)
-	}
 
-	var backendBlock *hcl.Block
-	if backends := content.Blocks.OfType(backend); len(backends) > 0 {
-		backendBlock = backends[0]
-	} else {
-		return reference, nil
-	}
+		anonLabel := newAnonLabel(block.HCLBody(), labelRange) + labelSuffix
+		// ensure our default settings
+		if backendBody == nil {
+			backendBody = defaultBackend
+		} else {
+			backendBody = hclbody.MergeBodies(defaultBackend, backendBody)
+		}
 
-	// Case: `backend {}`, anonymous backend.
-	if len(backendBlock.Labels) == 0 {
-		return backendBlock.Body, nil
+		backendBody, err = newBodyWithName(anonLabel, backendBody)
+		if err != nil {
+			return nil, err
+		}
 	}
 
-	// Case: `backend "reference" {}`, referenced backend.
-	refOverride, err := definedBackends.WithName(backendBlock.Labels[0])
+	// watch out for oauth blocks and nested backend definitions
+	oauth2Backend, err := newOAuthBackend(helper, backendBody)
 	if err != nil {
-		err.(hcl.Diagnostics)[0].Subject = &backendBlock.DefRange
-
-		// Case: referenced backend is not defined in definitions.
 		return nil, err
 	}
 
-	// link backend block name (label) to attribute 'name'
-	if syntaxBody, ok := backendBlock.Body.(*hclsyntax.Body); ok {
-		if refBody, ok := refOverride.(*hclsyntax.Body); ok {
-			syntaxBody.Attributes[nameLabel] = refBody.Attributes[nameLabel]
-		}
+	if oauth2Backend != nil {
+		wrapped := wrapOauth2Backend(oauth2Backend)
+		backendBody = hclbody.MergeBodies(backendBody, wrapped)
 	}
 
-	return hclbody.MergeBodies(refOverride, backendBlock.Body), nil
+	return backendBody, nil
 }
 
-// getBackendReference tries to fetch a backend from `definitions`
-// block by a reference name, e.g. `backend = "name"`.
-func getBackendReference(definedBackends Backends, be config.BackendReference) (hcl.Body, error) {
-	name := be.Reference()
+// getBackendReference reads a referenced backend name and the refined backend block content if any.
+func getBackendReference(inline config.Inline) (string, hcl.Body, error) {
+	var reference string
 
-	// backend string attribute just not set
-	if name == "" {
-		return nil, nil
-	}
-
-	reference, err := definedBackends.WithName(name)
-	if err != nil {
-		return nil, err // parse err
+	content, _, diags := inline.HCLBody().PartialContent(inline.Schema(true))
+	if diags.HasErrors() {
+		return "", nil, diags
 	}
 
-	// a name is given but we have no definition
-	if body, ok := be.(config.Inline); ok {
-		if b := body.HCLBody(); reference == nil && b != nil {
-			r := b.MissingItemRange()
-			return nil, hcl.Diagnostics{&hcl.Diagnostic{
-				Subject: &r,
-				Summary: fmt.Sprintf("backend reference '%s' is not defined", name),
-			}}
+	backends := content.Blocks.OfType(backend)
+	if len(backends) == 0 {
+		if beref, ok := inline.(config.BackendReference); ok {
+			if beref.Reference() != "" {
+				reference = beref.Reference()
+			}
 		}
+		return reference, nil, nil
 	}
 
-	return reference, nil
+	body := backends[0].Body
+	if len(backends[0].Labels) > 0 {
+		reference = backends[0].Labels[0]
+	}
+	return reference, body, nil
 }
 
-func newBackend(definedBackends Backends, inlineConfig config.Inline) (hcl.Body, error) {
-	bend, err := mergeBackendBodies(definedBackends, inlineConfig)
+// newOAuthBackend prepares a nested backend within a backend-oauth2 block.
+// TODO: Check a possible circular dependency with given parent backend(s).
+func newOAuthBackend(helper *helper, parent hcl.Body) (hcl.Body, error) {
+	innerContent, err := contentByType(oauth2, parent)
 	if err != nil {
 		return nil, err
 	}
 
-	if bend == nil {
-		// Create a default backend
-		bend = hclbody.New(&hcl.BodyContent{
-			Attributes: map[string]*hcl.Attribute{
-				"name": {
-					Name: "name",
-					Expr: &hclsyntax.LiteralValueExpr{
-						Val: cty.StringVal(defaultNameLabel),
-					},
-				},
-			},
-		})
+	oauthBlocks := innerContent.Blocks.OfType(oauth2)
+	if len(oauthBlocks) == 0 {
+		return nil, nil
 	}
 
-	oauth2Backend, err := newOAuthBackend(definedBackends, bend)
-	if err != nil {
-		return nil, err
+	// oauth block exists, read out backend configuration
+	oauthBody := oauthBlocks[0].Body
+	conf := &config.OAuth2ReqAuth{}
+	if diags := gohcl.DecodeBody(oauthBody, helper.context, conf); diags.HasErrors() {
+		return nil, diags
 	}
 
-	if oauth2Backend != nil {
-		wrapped := hclbody.New(&hcl.BodyContent{Blocks: []*hcl.Block{
-			{Type: oauth2, Body: hclbody.New(&hcl.BodyContent{Blocks: []*hcl.Block{
-				{Type: backend, Body: oauth2Backend},
-			}})},
-		}})
-		bend = hclbody.MergeBodies(bend, wrapped)
-	}
+	return PrepareBackend(helper, "", conf.TokenEndpoint, conf)
+}
 
-	return bend, nil
+func wrapOauth2Backend(content hcl.Body) hcl.Body {
+	b := hclbody.New(&hcl.BodyContent{
+		Blocks: []*hcl.Block{
+			{
+				Type: oauth2,
+				Body: newBackendBlock(content),
+			},
+		},
+	})
+	return b
 }
 
-func newOAuthBackend(definedBackends Backends, parent hcl.Body) (hcl.Body, error) {
-	innerContent, err := contentByType(oauth2, parent)
-	if err != nil {
+func newBackendBlock(content hcl.Body) hcl.Body {
+	return hclbody.New(&hcl.BodyContent{
+		Blocks: []*hcl.Block{
+			{
+				Type: backend,
+				Body: content,
+			},
+		},
+	})
+}
+
+func newBodyWithName(nameValue string, config hcl.Body) (hcl.Body, error) {
+	if err := validLabel(nameValue, getRange(config)); err != nil {
 		return nil, err
 	}
 
-	oauthBlocks := innerContent.Blocks.OfType(oauth2)
-	if len(oauthBlocks) == 0 {
-		return nil, nil
-	}
+	return hclbody.MergeBodies(
+		config,
+		hclbody.New(hclbody.NewContentWithAttrName("name", nameValue)),
+	), nil
+}
 
-	backendContent, err := contentByType(backend, oauthBlocks[0].Body)
-	if err != nil {
-		return nil, err
+func newAnonLabel(body hcl.Body, labelRange *hcl.Range) string {
+	const anon = "anonymous"
+	itemRange := labelRange
+	if itemRange == nil {
+		itemRange = getRange(body)
 	}
 
-	beConfig := &config.Backend{Remain: hclbody.New(backendContent)}
-
-	attrs, _ := oauthBlocks[0].Body.JustAttributes()
-	if attrs != nil && attrs["backend"] != nil {
-		val, _ := attrs["backend"].Expr.Value(nil)
+	return fmt.Sprintf("%s_%d_%d", anon,
+		itemRange.Start.Line,
+		itemRange.Start.Column,
+	)
+}
 
-		if ref := seetie.ValueToString(val); ref != "" {
-			beConfig.Name = ref
+func refineAnonLabel(attrName string, body hcl.Body) (labelRange *hcl.Range, labelSuffix string) {
+	if syntaxBody, ok := body.(*hclsyntax.Body); ok {
+		if attr, exist := syntaxBody.Attributes[attrName]; exist {
+			labelRange = &attr.NameRange
 		}
+		labelSuffix += "_" + attrName
 	}
-
-	oauthBackend, err := mergeBackendBodies(definedBackends, beConfig)
-	if err != nil {
-		return nil, err
-	}
-
-	return newBackend(definedBackends, &config.OAuth2ReqAuth{Remain: hclbody.New(&hcl.BodyContent{
-		Blocks: []*hcl.Block{
-			{Type: backend, Body: oauthBackend},
-		},
-	})})
+	return labelRange, labelSuffix
 }
diff --git a/config/configload/endpoint.go b/config/configload/endpoint.go
index a4dd2283..f798e449 100644
--- a/config/configload/endpoint.go
+++ b/config/configload/endpoint.go
@@ -36,20 +36,16 @@ func newCatchAllEndpoint() *config.Endpoint {
 	}
 }
 
-func refineEndpoints(definedBackends Backends, endpoints config.Endpoints, check bool) error {
+func refineEndpoints(helper *helper, endpoints config.Endpoints, check bool) error {
 	var err error
 
 	for _, endpoint := range endpoints {
 		if check && endpoint.Pattern == "" {
-			var r hcl.Range
+			var r *hcl.Range
 			if endpoint.Remain != nil {
-				r = endpoint.Remain.MissingItemRange()
+				r = getRange(endpoint.Remain)
 			}
-			return hcl.Diagnostics{&hcl.Diagnostic{
-				Severity: hcl.DiagError,
-				Summary:  "endpoint: missing path pattern",
-				Subject:  &r,
-			}}
+			return newDiagErr(r, "endpoint: missing path pattern")
 		}
 
 		endpointContent := bodyToContent(endpoint.Remain)
@@ -64,18 +60,16 @@ func refineEndpoints(definedBackends Backends, endpoints config.Endpoints, check
 		requests := endpointContent.Blocks.OfType(request)
 
 		if check && len(proxies)+len(requests) == 0 && endpoint.Response == nil {
-			return hcl.Diagnostics{&hcl.Diagnostic{
-				Severity: hcl.DiagError,
-				Summary:  "missing 'default' proxy or request block, or a response definition",
-				Subject:  &endpointContent.MissingItemRange,
-			}}
+			return newDiagErr(&endpointContent.MissingItemRange,
+				"missing 'default' proxy or request block, or a response definition",
+			)
 		}
 
 		proxyRequestLabelRequired := len(proxies)+len(requests) > 1
 
 		for _, proxyBlock := range proxies {
 			proxyConfig := &config.Proxy{}
-			if diags := gohcl.DecodeBody(proxyBlock.Body, envContext, proxyConfig); diags.HasErrors() {
+			if diags := gohcl.DecodeBody(proxyBlock.Body, helper.context, proxyConfig); diags.HasErrors() {
 				return diags
 			}
 			if len(proxyBlock.Labels) > 0 {
@@ -105,7 +99,7 @@ func refineEndpoints(definedBackends Backends, endpoints config.Endpoints, check
 
 			proxyConfig.Remain = proxyBlock.Body
 
-			proxyConfig.Backend, err = newBackend(definedBackends, proxyConfig)
+			proxyConfig.Backend, err = PrepareBackend(helper, "", "", proxyConfig)
 			if err != nil {
 				return err
 			}
@@ -115,7 +109,7 @@ func refineEndpoints(definedBackends Backends, endpoints config.Endpoints, check
 
 		for _, reqBlock := range requests {
 			reqConfig := &config.Request{}
-			if diags := gohcl.DecodeBody(reqBlock.Body, envContext, reqConfig); diags.HasErrors() {
+			if diags := gohcl.DecodeBody(reqBlock.Body, helper.context, reqConfig); diags.HasErrors() {
 				return diags
 			}
 
@@ -141,7 +135,7 @@ func refineEndpoints(definedBackends Backends, endpoints config.Endpoints, check
 
 			reqConfig.Remain = hclbody.MergeBodies(leftOvers, hclbody.New(content))
 
-			reqConfig.Backend, err = newBackend(definedBackends, reqConfig)
+			reqConfig.Backend, err = PrepareBackend(helper, "", "", reqConfig)
 			if err != nil {
 				return err
 			}
@@ -150,30 +144,23 @@ func refineEndpoints(definedBackends Backends, endpoints config.Endpoints, check
 		}
 
 		if endpoint.Response != nil {
-			content, _, _ := endpoint.Response.HCLBody().PartialContent(config.ResponseInlineSchema)
-			_, existsBody := content.Attributes["body"]
-			_, existsJsonBody := content.Attributes["json_body"]
-			if existsBody && existsJsonBody {
-				return hcl.Diagnostics{&hcl.Diagnostic{
-					Severity: hcl.DiagError,
-					Summary:  "response can only have one of body or json_body attributes",
-					Subject:  &content.Attributes["body"].Range,
-				}}
+			if err = verifyResponseBodyAttrs(endpoint.Response.HCLBody()); err != nil {
+				return err
 			}
 		}
 
 		names := map[string]hcl.Body{}
 		unique := map[string]struct{}{}
-		itemRange := endpoint.Remain.MissingItemRange()
+		subject := endpoint.Remain.MissingItemRange()
 		for _, p := range endpoint.Proxies {
 			names[p.Name] = p.Remain
 
-			if err := validLabelName(p.Name, &itemRange); err != nil {
+			if err = validLabel(p.Name, &subject); err != nil {
 				return err
 			}
 
 			if proxyRequestLabelRequired {
-				if err := uniqueLabelName(unique, p.Name, &itemRange); err != nil {
+				if err = uniqueLabelName(unique, p.Name, &subject); err != nil {
 					return err
 				}
 			}
@@ -182,23 +169,19 @@ func refineEndpoints(definedBackends Backends, endpoints config.Endpoints, check
 		for _, r := range endpoint.Requests {
 			names[r.Name] = r.Remain
 
-			if err = validLabelName(r.Name, &itemRange); err != nil {
+			if err = validLabel(r.Name, &subject); err != nil {
 				return err
 			}
 
 			if proxyRequestLabelRequired {
-				if err = uniqueLabelName(unique, r.Name, &itemRange); err != nil {
+				if err = uniqueLabelName(unique, r.Name, &subject); err != nil {
 					return err
 				}
 			}
 		}
 
 		if _, ok := names[defaultNameLabel]; check && !ok && endpoint.Response == nil {
-			return hcl.Diagnostics{&hcl.Diagnostic{
-				Severity: hcl.DiagError,
-				Summary:  "Missing a 'default' proxy or request definition, or a response block",
-				Subject:  &itemRange,
-			}}
+			return newDiagErr(&subject, "Missing a 'default' proxy or request definition, or a response block")
 		}
 
 		if err = buildSequences(names, endpoint); err != nil {
@@ -206,7 +189,7 @@ func refineEndpoints(definedBackends Backends, endpoints config.Endpoints, check
 		}
 
 		epErrorHandler := collect.ErrorHandlerSetters(endpoint)
-		if err = configureErrorHandler(epErrorHandler, definedBackends); err != nil {
+		if err = configureErrorHandler(epErrorHandler, helper); err != nil {
 			return err
 		}
 	}
diff --git a/config/configload/endpoint_sequence.go b/config/configload/endpoint_sequence.go
index 0109af2c..ba9001de 100644
--- a/config/configload/endpoint_sequence.go
+++ b/config/configload/endpoint_sequence.go
@@ -35,14 +35,14 @@ func buildSequences(names map[string]hcl.Body, endpoint *config.Endpoint) (err e
 
 		seq, exist := sequences[name]
 		if !exist {
-			seq = &config.Sequence{Name: name, BodyRange: b.MissingItemRange()}
+			seq = &config.Sequence{Name: name, BodyRange: getRange(b)}
 			sequences[name] = seq
 		}
 
 		for _, r := range refs {
 			ref, ok := sequences[r]
 			if !ok {
-				ref = &config.Sequence{Name: r, BodyRange: b.MissingItemRange()}
+				ref = &config.Sequence{Name: r, BodyRange: getRange(b)}
 				sequences[r] = ref
 			}
 			// Do not add ourselves
diff --git a/config/configload/error_handler.go b/config/configload/error_handler.go
index e44c13a7..ab929330 100644
--- a/config/configload/error_handler.go
+++ b/config/configload/error_handler.go
@@ -17,7 +17,7 @@ type kindContent struct {
 	kinds []string
 }
 
-func configureErrorHandler(setter []collect.ErrorHandlerSetter, definedBackends Backends) error {
+func configureErrorHandler(setter []collect.ErrorHandlerSetter, helper *helper) error {
 	for _, ehs := range setter {
 		body, ok := ehs.(config.Body)
 		if !ok {
@@ -30,7 +30,7 @@ func configureErrorHandler(setter []collect.ErrorHandlerSetter, definedBackends
 		}
 
 		for _, hc := range ehc {
-			errHandlerConf, confErr := newErrorHandlerConfig(hc, definedBackends)
+			errHandlerConf, confErr := newErrorHandlerConfig(hc, helper)
 			if confErr != nil {
 				return confErr
 			}
@@ -129,9 +129,9 @@ func newKindsFromLabels(block *hcl.Block) ([]string, error) {
 	return allKinds, nil
 }
 
-func newErrorHandlerConfig(content kindContent, definedBackends Backends) (*config.ErrorHandler, error) {
+func newErrorHandlerConfig(content kindContent, helper *helper) (*config.ErrorHandler, error) {
 	errHandlerConf := &config.ErrorHandler{Kinds: content.kinds}
-	if d := gohcl.DecodeBody(content.body, envContext, errHandlerConf); d.HasErrors() {
+	if d := gohcl.DecodeBody(content.body, helper.context, errHandlerConf); d.HasErrors() {
 		return nil, d
 	}
 
@@ -141,7 +141,7 @@ func newErrorHandlerConfig(content kindContent, definedBackends Backends) (*conf
 		Remain:    content.body,
 	}
 
-	if err := refineEndpoints(definedBackends, config.Endpoints{ep}, false); err != nil {
+	if err := refineEndpoints(helper, config.Endpoints{ep}, false); err != nil {
 		return nil, err
 	}
 
diff --git a/config/configload/helper.go b/config/configload/helper.go
new file mode 100644
index 00000000..9a52d82f
--- /dev/null
+++ b/config/configload/helper.go
@@ -0,0 +1,113 @@
+package configload
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/hashicorp/hcl/v2"
+	"github.com/hashicorp/hcl/v2/gohcl"
+
+	"github.com/avenga/couper/config"
+	"github.com/avenga/couper/eval"
+)
+
+type helper struct {
+	config       *config.Couper
+	context      *hcl.EvalContext
+	content      *hcl.BodyContent
+	defsBackends map[string]hcl.Body
+}
+
+// newHelper creates a container with some methods to keep things simple here and there.
+func newHelper(body hcl.Body, src []byte, filename, dirPath string) (*helper, error) {
+	defaultsBlock := &config.DefaultsBlock{}
+	if diags := gohcl.DecodeBody(body, nil, defaultsBlock); diags.HasErrors() {
+		return nil, diags
+	}
+
+	defSettings := config.DefaultSettings
+
+	couperConfig := &config.Couper{
+		Context:     eval.NewContext([][]byte{src}, defaultsBlock.Defaults),
+		Definitions: &config.Definitions{},
+		Defaults:    defaultsBlock.Defaults,
+		Filename:    filename,
+		Dirpath:     dirPath,
+		Settings:    &defSettings,
+	}
+
+	schema, _ := gohcl.ImpliedBodySchema(couperConfig)
+	content, diags := body.Content(schema)
+	if content == nil { // reference diags only for missing content, due to optional server label
+		return nil, fmt.Errorf("invalid configuration: %w", diags)
+	}
+
+	return &helper{
+		config:       couperConfig,
+		content:      content,
+		context:      couperConfig.Context.(*eval.Context).HCLContext(),
+		defsBackends: make(map[string]hcl.Body),
+	}, nil
+}
+
+func (h *helper) addBackend(block *hcl.Block) error {
+	name := block.Labels[0]
+
+	if _, ok := h.defsBackends[name]; ok {
+		return newDiagErr(&block.LabelRanges[0],
+			fmt.Sprintf("duplicate backend name: %q", name))
+	} else if strings.HasPrefix(name, "anonymous_") {
+		return newDiagErr(&block.LabelRanges[0],
+			fmt.Sprintf("backend name must not start with 'anonymous_': %q", name))
+	}
+
+	backendBody, err := newBodyWithName(name, block.Body)
+	if err != nil {
+		return err
+	}
+
+	h.defsBackends[name] = backendBody
+	return nil
+}
+
+func (h *helper) configureDefinedBackends() error {
+	for name, b := range h.defsBackends {
+		be, err := PrepareBackend(h, "_init", "", &config.Backend{Name: name, Remain: b})
+		if err != nil {
+			return err
+		}
+		h.config.Definitions.Backend = append(
+			h.config.Definitions.Backend,
+			&config.Backend{Remain: be, Name: name},
+		)
+
+		h.defsBackends[name] = be
+	}
+	return nil
+}
+
+func (h *helper) configureACBackends() error {
+	var acs []config.BackendInitialization
+	for _, ac := range h.config.Definitions.JWT {
+		acs = append(acs, ac)
+	}
+	for _, ac := range h.config.Definitions.OAuth2AC {
+		acs = append(acs, ac)
+	}
+
+	// TODO: remove with 1.8
+	h.config.Definitions.OIDC = append(h.config.Definitions.OIDC, h.config.Definitions.BetaOIDC...)
+
+	for _, ac := range append(h.config.Definitions.OIDC) {
+		acs = append(acs, ac)
+	}
+
+	for _, ac := range acs {
+		if err := ac.Prepare(func(attr string, attrVal string, i config.Inline) (hcl.Body, error) {
+			return PrepareBackend(h, attr, attrVal, i) // wrap helper
+		}); err != nil {
+			return err
+		}
+	}
+	return nil
+}
diff --git a/config/configload/load.go b/config/configload/load.go
index be1d41bb..b1f6678c 100644
--- a/config/configload/load.go
+++ b/config/configload/load.go
@@ -5,7 +5,6 @@ import (
 	"io/ioutil"
 	"os"
 	"path/filepath"
-	"regexp"
 	"strings"
 
 	"github.com/hashicorp/hcl/v2"
@@ -38,7 +37,6 @@ const (
 	defaultNameLabel = "default"
 )
 
-var regexProxyRequestLabel = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
 var defaultsConfig *config.Defaults
 var evalContext *eval.Context
 var envContext *hcl.EvalContext
@@ -195,7 +193,7 @@ func LoadFiles(filePath, dirPath string) (*config.Couper, error) {
 		Blocks: configBlocks,
 	}
 
-	return LoadConfig(configBody, filepath.Base(filePath), dirPath)
+	return LoadConfig(configBody, srcBytes[0], filepath.Base(filePath), dirPath)
 }
 
 func LoadBytes(src []byte, filename string) (*config.Couper, error) {
@@ -204,42 +202,26 @@ func LoadBytes(src []byte, filename string) (*config.Couper, error) {
 		return nil, diags
 	}
 
-	if diags := updateContext(hclBody, [][]byte{src}); diags.HasErrors() {
+	if diags = updateContext(hclBody, [][]byte{src}); diags.HasErrors() {
 		return nil, diags
 	}
 
-	return LoadConfig(hclBody, filename, "")
+	return LoadConfig(hclBody, src, filename, "")
 }
 
-func LoadConfig(body hcl.Body, filename, dirPath string) (*config.Couper, error) {
+func LoadConfig(body hcl.Body, src []byte, filename, dirPath string) (*config.Couper, error) {
+	var err error
+
 	if diags := ValidateConfigSchema(body, &config.Couper{}); diags.HasErrors() {
 		return nil, diags
 	}
 
-	defaults := config.DefaultSettings
-	defaults.AcceptForwarded = &config.AcceptForwarded{}
-
-	couperConfig := &config.Couper{
-		Context:     evalContext,
-		Definitions: &config.Definitions{},
-		Defaults:    defaultsConfig,
-		Filename:    filename,
-		Dirpath:     dirPath,
-		Settings:    &defaults,
-	}
-
-	schema, _ := gohcl.ImpliedBodySchema(couperConfig)
-	content, diags := body.Content(schema)
-	if content == nil {
-		return nil, fmt.Errorf("invalid configuration: %w", diags)
+	helper, err := newHelper(body, src, filename, dirPath)
+	if err != nil {
+		return nil, err
 	}
 
-	// Read possible reference definitions first. Those are the
-	// base for refinement merges during server block read out.
-	var definedBackends Backends
-	var err error
-
-	for _, outerBlock := range content.Blocks {
+	for _, outerBlock := range helper.content.Blocks {
 		switch outerBlock.Type {
 		case definitions:
 			backendContent, leftOver, diags := outerBlock.Body.PartialContent(backendBlockSchema)
@@ -247,107 +229,46 @@ func LoadConfig(body hcl.Body, filename, dirPath string) (*config.Couper, error)
 				return nil, diags
 			}
 
+			// backends first
 			if backendContent != nil {
 				for _, be := range backendContent.Blocks {
-					name := be.Labels[0]
-					ref, _ := definedBackends.WithName(name)
-					if ref != nil {
-						return nil, hcl.Diagnostics{&hcl.Diagnostic{
-							Severity: hcl.DiagError,
-							Summary:  fmt.Sprintf("duplicate backend name: %q", name),
-							Subject:  &be.LabelRanges[0],
-						}}
+					if err = helper.addBackend(be); err != nil {
+						return nil, err
 					}
-
-					definedBackends = append(definedBackends, NewBackend(name, be.Body))
 				}
-			}
-
-			if diags = gohcl.DecodeBody(leftOver, envContext, couperConfig.Definitions); diags.HasErrors() {
-				return nil, diags
-			}
 
-			for _, oauth2Config := range couperConfig.Definitions.OAuth2AC {
-				bodyContent, _, diags := oauth2Config.HCLBody().PartialContent(oauth2Config.Schema(true))
-				if diags.HasErrors() {
-					return nil, diags
-				}
-				oauth2Config.BodyContent = bodyContent
-
-				oauth2Config.Backend, err = newBackend(definedBackends, oauth2Config)
-				if err != nil {
+				if err = helper.configureDefinedBackends(); err != nil {
 					return nil, err
 				}
 			}
 
-			for _, oidcConfig := range couperConfig.Definitions.OIDC {
-				bodyContent, _, diags := oidcConfig.HCLBody().PartialContent(oidcConfig.Schema(true))
-				if diags.HasErrors() {
-					return nil, diags
-				}
-				oidcConfig.BodyContent = bodyContent
-
-				oidcConfig.Backend, err = newBackend(definedBackends, oidcConfig)
-				if err != nil {
-					return nil, err
-				}
-			}
-			// TODO remove for version 1.8
-			for _, oidcConfig := range couperConfig.Definitions.BetaOIDC {
-				bodyContent, _, diags := oidcConfig.HCLBody().PartialContent(oidcConfig.Schema(true))
-				if diags.HasErrors() {
-					return nil, diags
-				}
-				oidcConfig.BodyContent = bodyContent
-
-				oidcConfig.Backend, err = newBackend(definedBackends, oidcConfig)
-				if err != nil {
-					return nil, err
-				}
+			// decode all other blocks into definition struct
+			if diags = gohcl.DecodeBody(leftOver, helper.context, helper.config.Definitions); diags.HasErrors() {
+				return nil, diags
 			}
 
-			for _, jwtConfig := range couperConfig.Definitions.JWT {
-				if jwtConfig.JWKsURL != "" {
-					bodyContent, _, diags := jwtConfig.HCLBody().PartialContent(jwtConfig.Schema(true))
-					if diags.HasErrors() {
-						return nil, diags
-					}
-					jwtConfig.BodyContent = bodyContent
-
-					jwtConfig.Backend, err = newBackend(definedBackends, jwtConfig)
-					if err != nil {
-						return nil, err
-					}
-
-					jwtConfig.BackendName = ""
-				}
-				if err = jwtConfig.Check(); err != nil {
-					return nil, errors.Configuration.Label(jwtConfig.Name).With(err)
-				}
+			if err = helper.configureACBackends(); err != nil {
+				return nil, err
 			}
 
-			acErrorHandler := collect.ErrorHandlerSetters(couperConfig.Definitions)
-			if err := configureErrorHandler(acErrorHandler, definedBackends); err != nil {
+			acErrorHandler := collect.ErrorHandlerSetters(helper.config.Definitions)
+			if err = configureErrorHandler(acErrorHandler, helper); err != nil {
 				return nil, err
 			}
 
 		case settings:
-			if diags = gohcl.DecodeBody(outerBlock.Body, envContext, couperConfig.Settings); diags.HasErrors() {
+			if diags := gohcl.DecodeBody(outerBlock.Body, helper.context, helper.config.Settings); diags.HasErrors() {
 				return nil, diags
 			}
-			if err := couperConfig.Settings.SetAcceptForwarded(); err != nil {
-				diag := &hcl.Diagnostic{
-					Severity: hcl.DiagError,
-					Summary:  fmt.Sprintf("invalid accept_forwarded_url: %q", err),
-					Subject:  &outerBlock.DefRange,
-				}
-				return nil, diag
+
+			if err = helper.config.Settings.SetAcceptForwarded(); err != nil {
+				return nil, newDiagErr(&outerBlock.DefRange, fmt.Sprintf("invalid accept_forwarded_url: %q", err))
 			}
 		}
 	}
 
 	// Prepare dynamic functions
-	for _, profile := range couperConfig.Definitions.JWTSigningProfile {
+	for _, profile := range helper.config.Definitions.JWTSigningProfile {
 		if profile.Headers != nil {
 			expression, _ := profile.Headers.Value(nil)
 			headers := seetie.ValueToMap(expression)
@@ -355,13 +276,12 @@ func LoadConfig(body hcl.Body, filename, dirPath string) (*config.Couper, error)
 			var errorMessage string
 			if _, exists := headers["alg"]; exists {
 				errorMessage = `"alg" cannot be set via "headers"`
-			} else if _, exists := headers["typ"]; exists {
+			} else if _, exists = headers["typ"]; exists {
 				errorMessage = `"typ" cannot be set via "headers"`
 			}
 
 			if errorMessage != "" {
-				err := fmt.Errorf(errorMessage)
-				return nil, errors.Configuration.Label(profile.Name).With(err)
+				return nil, errors.Configuration.Label(profile.Name).With(fmt.Errorf(errorMessage))
 			}
 		}
 
@@ -372,7 +292,7 @@ func LoadConfig(body hcl.Body, filename, dirPath string) (*config.Couper, error)
 		profile.KeyBytes = key
 	}
 
-	for _, saml := range couperConfig.Definitions.SAML {
+	for _, saml := range helper.config.Definitions.SAML {
 		metadata, err := reader.ReadFromFile("saml2 idp_metadata_file", saml.IdpMetadataFile)
 		if err != nil {
 			return nil, errors.Configuration.Label(saml.Name).With(err)
@@ -381,7 +301,7 @@ func LoadConfig(body hcl.Body, filename, dirPath string) (*config.Couper, error)
 	}
 
 	jwtSigningConfigs := make(map[string]*lib.JWTSigningConfig)
-	for _, profile := range couperConfig.Definitions.JWTSigningProfile {
+	for _, profile := range helper.config.Definitions.JWTSigningProfile {
 		if _, exists := jwtSigningConfigs[profile.Name]; exists {
 			return nil, errors.Configuration.Messagef("jwt_signing_profile block with label %s already defined", profile.Name)
 		}
@@ -391,7 +311,7 @@ func LoadConfig(body hcl.Body, filename, dirPath string) (*config.Couper, error)
 		}
 		jwtSigningConfigs[profile.Name] = signConf
 	}
-	for _, jwt := range couperConfig.Definitions.JWT {
+	for _, jwt := range helper.config.Definitions.JWT {
 		signConf, err := lib.NewJWTSigningConfigFromJWT(jwt)
 		if err != nil {
 			return nil, errors.Configuration.Label(jwt.Name).With(err)
@@ -404,15 +324,15 @@ func LoadConfig(body hcl.Body, filename, dirPath string) (*config.Couper, error)
 		}
 	}
 
-	couperConfig.Context = evalContext.
+	helper.config.Context = helper.config.Context.(*eval.Context).
 		WithJWTSigningConfigs(jwtSigningConfigs).
-		WithOAuth2AC(couperConfig.Definitions.OAuth2AC).
-		WithSAML(couperConfig.Definitions.SAML)
+		WithOAuth2AC(helper.config.Definitions.OAuth2AC).
+		WithSAML(helper.config.Definitions.SAML)
 
 	// Read per server block and merge backend settings which results in a final server configuration.
 	for _, serverBlock := range bodyToContent(body).Blocks.OfType(server) {
 		serverConfig := &config.Server{}
-		if diags = gohcl.DecodeBody(serverBlock.Body, envContext, serverConfig); diags.HasErrors() {
+		if diags := gohcl.DecodeBody(serverBlock.Body, helper.context, serverConfig); diags.HasErrors() {
 			return nil, diags
 		}
 
@@ -424,7 +344,7 @@ func LoadConfig(body hcl.Body, filename, dirPath string) (*config.Couper, error)
 		// Read api blocks and merge backends with server and definitions backends.
 		for _, apiBlock := range bodyToContent(serverConfig.Remain).Blocks.OfType(api) {
 			apiConfig := &config.API{}
-			if diags = gohcl.DecodeBody(apiBlock.Body, envContext, apiConfig); diags.HasErrors() {
+			if diags := gohcl.DecodeBody(apiBlock.Body, helper.context, apiConfig); diags.HasErrors() {
 				return nil, diags
 			}
 
@@ -438,7 +358,7 @@ func LoadConfig(body hcl.Body, filename, dirPath string) (*config.Couper, error)
 				}
 			}
 
-			err := refineEndpoints(definedBackends, apiConfig.Endpoints, true)
+			err = refineEndpoints(helper, apiConfig.Endpoints, true)
 			if err != nil {
 				return nil, err
 			}
@@ -447,23 +367,23 @@ func LoadConfig(body hcl.Body, filename, dirPath string) (*config.Couper, error)
 			serverConfig.APIs = append(serverConfig.APIs, apiConfig)
 
 			apiErrorHandler := collect.ErrorHandlerSetters(apiConfig)
-			if err = configureErrorHandler(apiErrorHandler, definedBackends); err != nil {
+			if err = configureErrorHandler(apiErrorHandler, helper); err != nil {
 				return nil, err
 			}
 		}
 
 		// standalone endpoints
-		err := refineEndpoints(definedBackends, serverConfig.Endpoints, true)
+		err = refineEndpoints(helper, serverConfig.Endpoints, true)
 		if err != nil {
 			return nil, err
 		}
 
-		couperConfig.Servers = append(couperConfig.Servers, serverConfig)
+		helper.config.Servers = append(helper.config.Servers, serverConfig)
 	}
 
-	if len(couperConfig.Servers) == 0 {
+	if len(helper.config.Servers) == 0 {
 		return nil, fmt.Errorf("configuration error: missing 'server' block")
 	}
 
-	return couperConfig, nil
+	return helper.config, nil
 }
diff --git a/config/configload/schema.go b/config/configload/schema.go
index 57de9b3d..2dc87480 100644
--- a/config/configload/schema.go
+++ b/config/configload/schema.go
@@ -316,7 +316,7 @@ func uniqueErrors(errors hcl.Diagnostics) hcl.Diagnostics {
 
 func bodyToContent(body hcl.Body) *hcl.BodyContent {
 	content := &hcl.BodyContent{
-		MissingItemRange: body.MissingItemRange(),
+		MissingItemRange: *getRange(body),
 	}
 	b, ok := body.(*hclsyntax.Body)
 	if !ok {
@@ -369,3 +369,16 @@ func contentByType(blockType string, body hcl.Body) (*hcl.BodyContent, error) {
 	}
 	return content, nil
 }
+
+func getRange(body hcl.Body) *hcl.Range {
+	if body == nil {
+		return &hcl.Range{}
+	}
+
+	if b, ok := body.(*hclsyntax.Body); ok {
+		return &b.SrcRange
+	}
+
+	r := body.MissingItemRange()
+	return &r
+}
diff --git a/config/configload/validate.go b/config/configload/validate.go
index 74b11428..2619b1e3 100644
--- a/config/configload/validate.go
+++ b/config/configload/validate.go
@@ -5,39 +5,47 @@ import (
 	"regexp"
 
 	"github.com/hashicorp/hcl/v2"
+
+	"github.com/avenga/couper/config"
 )
 
+var regexLabel = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
+
 // https://datatracker.ietf.org/doc/html/rfc7231#section-4
 // https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6
 var methodRegExp = regexp.MustCompile("^[!#$%&'*+\\-.\\^_`|~0-9a-zA-Z]+$")
 
-func validLabelName(name string, hr *hcl.Range) error {
-	if !regexProxyRequestLabel.MatchString(name) {
-		return hcl.Diagnostics{&hcl.Diagnostic{
-			Severity: hcl.DiagError,
-			Summary:  "label contains invalid character(s), allowed are 'a-z', 'A-Z', '0-9' and '_'",
-			Subject:  hr,
-		}}
+func newDiagErr(subject *hcl.Range, summary string) error {
+	return hcl.Diagnostics{&hcl.Diagnostic{
+		Severity: hcl.DiagError,
+		Summary:  summary,
+		Subject:  subject,
+	}}
+}
+
+func validLabel(name string, subject *hcl.Range) error {
+	if name == "" {
+		return newDiagErr(subject, "label is empty")
+	}
+
+	if !regexLabel.MatchString(name) {
+		return newDiagErr(subject, "label contains invalid character(s), allowed are 'a-z', 'A-Z', '0-9' and '_'")
 	}
+
 	return nil
 }
 
 func uniqueLabelName(unique map[string]struct{}, name string, hr *hcl.Range) error {
 	if _, exist := unique[name]; exist {
 		if name == defaultNameLabel {
-			return hcl.Diagnostics{&hcl.Diagnostic{
-				Severity: hcl.DiagError,
-				Summary:  "proxy and request labels are required and only one 'default' label is allowed",
-				Subject:  hr,
-			}}
+			return newDiagErr(hr, "proxy and request labels are required and only one 'default' label is allowed")
 		}
-		return hcl.Diagnostics{&hcl.Diagnostic{
-			Severity: hcl.DiagError,
-			Summary:  fmt.Sprintf("proxy and request labels are required and must be unique: %q", name),
-			Subject:  hr,
-		}}
+
+		return newDiagErr(hr, fmt.Sprintf("proxy and request labels are required and must be unique: %q", name))
 	}
+
 	unique[name] = struct{}{}
+
 	return nil
 }
 
@@ -45,16 +53,72 @@ func verifyBodyAttributes(content *hcl.BodyContent) error {
 	_, existsBody := content.Attributes["body"]
 	_, existsFormBody := content.Attributes["form_body"]
 	_, existsJsonBody := content.Attributes["json_body"]
+
 	if existsBody && existsFormBody || existsBody && existsJsonBody || existsFormBody && existsJsonBody {
 		rangeAttr := "body"
 		if !existsBody {
 			rangeAttr = "form_body"
 		}
-		return hcl.Diagnostics{&hcl.Diagnostic{
-			Severity: hcl.DiagError,
-			Summary:  "request can only have one of body, form_body or json_body attributes",
-			Subject:  &content.Attributes[rangeAttr].Range,
-		}}
+
+		return newDiagErr(&content.Attributes[rangeAttr].Range,
+			"request can only have one of body, form_body or json_body attributes")
+	}
+
+	return nil
+}
+
+func verifyResponseBodyAttrs(b hcl.Body) error {
+	content, _, _ := b.PartialContent(config.ResponseInlineSchema)
+	_, existsBody := content.Attributes["body"]
+	_, existsJsonBody := content.Attributes["json_body"]
+	if existsBody && existsJsonBody {
+		return newDiagErr(&content.Attributes["body"].Range, "response can only have one of body or json_body attributes")
+	}
+	return nil
+}
+
+var invalidAttributes = []string{"disable_certificate_validation", "disable_connection_reuse", "http2", "max_connections"}
+var openApiBlockSchema = &hcl.BodySchema{
+	Blocks: []hcl.BlockHeaderSchema{
+		{
+			Type: "openapi",
+		},
+	},
+}
+
+func invalidRefinement(body hcl.Body) error {
+	const message = "backend reference: refinement for %q is not permitted"
+	attrs, _ := body.JustAttributes()
+	if attrs == nil {
+		return nil
+	}
+	for _, name := range invalidAttributes {
+		attr, exist := attrs[name]
+		if exist {
+			return newDiagErr(&attr.NameRange, fmt.Sprintf(message, attr.Name))
+		}
+	}
+
+	content, _, _ := body.PartialContent(openApiBlockSchema)
+	if content != nil && len(content.Blocks.OfType("openapi")) > 0 {
+		return newDiagErr(&content.Blocks.OfType("openapi")[0].DefRange, fmt.Sprintf(message, "openapi"))
+	}
+
+	return nil
+}
+
+func invalidOriginRefinement(reference, params hcl.Body) error {
+	const origin = "origin"
+	refAttrs, _ := reference.JustAttributes()
+	paramAttrs, _ := params.JustAttributes()
+
+	refOrigin, _ := refAttrs[origin]
+	paramOrigin, _ := paramAttrs[origin]
+
+	if paramOrigin != nil && refOrigin != nil {
+		if paramOrigin.Expr != refOrigin.Expr {
+			return newDiagErr(&paramOrigin.Range, fmt.Sprintf("backend reference: origin must be equal"))
+		}
 	}
 	return nil
 }
@@ -62,11 +126,7 @@ func verifyBodyAttributes(content *hcl.BodyContent) error {
 func validMethods(methods []string, hr *hcl.Range) error {
 	for _, method := range methods {
 		if !methodRegExp.MatchString(method) {
-			return hcl.Diagnostics{&hcl.Diagnostic{
-				Severity: hcl.DiagError,
-				Summary:  "method contains invalid character(s)",
-				Subject:  hr,
-			}}
+			return newDiagErr(hr, "method contains invalid character(s)")
 		}
 	}
 
diff --git a/config/field.go b/config/field.go
new file mode 100644
index 00000000..05d114fc
--- /dev/null
+++ b/config/field.go
@@ -0,0 +1,52 @@
+package config
+
+import (
+	"reflect"
+	"strings"
+)
+
+func BackendAttrFields(obj interface{}) []string {
+	const filter = "_backend"
+	var result []string
+
+	t := reflect.TypeOf(obj)
+	if t.Kind() == reflect.Ptr {
+		t = t.Elem()
+	}
+
+	for i := 0; i < t.NumField(); i++ {
+		f := t.Field(i)
+		tagName := attrFromField(f)
+		if tagName != "" && strings.HasSuffix(tagName, filter) {
+			result = append(result, tagName)
+		}
+	}
+
+	return result
+}
+
+func AttrValueFromTagField(name string, obj interface{}) string {
+	t := reflect.TypeOf(obj)
+	if t.Kind() == reflect.Ptr {
+		t = t.Elem()
+	}
+	for i := t.NumField() - 1; i > 0; i-- {
+		if lookup, _ := t.Field(i).Tag.Lookup("hcl"); strings.HasPrefix(lookup, name) {
+			return valueOf(obj).Field(i).String()
+		}
+	}
+
+	return ""
+}
+
+func attrFromField(field reflect.StructField) string {
+	return strings.Split(field.Tag.Get("hcl"), ",")[0]
+}
+
+func valueOf(obj interface{}) reflect.Value {
+	val := reflect.ValueOf(obj)
+	if val.Kind() == reflect.Ptr {
+		val = val.Elem()
+	}
+	return val
+}
diff --git a/config/inline.go b/config/inline.go
index ee3a34dd..5e1d974c 100644
--- a/config/inline.go
+++ b/config/inline.go
@@ -14,6 +14,12 @@ type BackendReference interface {
 	Reference() string
 }
 
+type PrepareBackendFunc func(attr string, val string, body Inline) (hcl.Body, error)
+
+type BackendInitialization interface {
+	Prepare(backendFunc PrepareBackendFunc) error
+}
+
 // Body defines the <Body> interface.
 type Body interface {
 	HCLBody() hcl.Body
diff --git a/config/oauth2.go b/config/oauth2.go
index 63e4ad25..cbdbcb97 100644
--- a/config/oauth2.go
+++ b/config/oauth2.go
@@ -1,9 +1,5 @@
 package config
 
-import (
-	"github.com/hashicorp/hcl/v2"
-)
-
 const (
 	CcmS256 = "ccm_s256"
 )
@@ -43,7 +39,6 @@ type OAuth2AcClient interface {
 	GetName() string
 	// GetVerifierMethod retrieves the verifier method (ccm_s256, nonce or state)
 	GetVerifierMethod(uid string) (string, error)
-	GetBodyContent() *hcl.BodyContent
 }
 
 // OAuth2Authorization represents the configuration for the OAuth2 authorization URL function
diff --git a/config/oauth2ra.go b/config/oauth2ra.go
index 6efa7ad1..aee0aa52 100644
--- a/config/oauth2ra.go
+++ b/config/oauth2ra.go
@@ -20,7 +20,7 @@ var (
 	_ OAuth2AS         = &OAuth2ReqAuth{}
 )
 
-// OAuth2ReqAuth represents the the oauth2 block in a backend block.
+// OAuth2ReqAuth represents the oauth2 block in a backend block.
 type OAuth2ReqAuth struct {
 	BackendName             string   `hcl:"backend,optional"`
 	ClientID                string   `hcl:"client_id"`
diff --git a/config/request/context_key.go b/config/request/context_key.go
index cf351cfb..ff49191d 100644
--- a/config/request/context_key.go
+++ b/config/request/context_key.go
@@ -4,11 +4,13 @@ type ContextKey uint8
 
 const (
 	ContextType ContextKey = iota
-	ContextVariablesSynced
 	APIName
 	AccessControls
 	BackendName
+	BackendParams
 	BufferOptions
+	ConnectTimeout
+	ContextVariablesSynced
 	Endpoint
 	EndpointExpectedStatus
 	EndpointKind
diff --git a/config/runtime/backend.go b/config/runtime/backend.go
new file mode 100644
index 00000000..14c457f1
--- /dev/null
+++ b/config/runtime/backend.go
@@ -0,0 +1,147 @@
+package runtime
+
+import (
+	"math"
+	"net/http"
+
+	"github.com/hashicorp/hcl/v2"
+	"github.com/hashicorp/hcl/v2/gohcl"
+	"github.com/sirupsen/logrus"
+
+	"github.com/avenga/couper/backend"
+	"github.com/avenga/couper/cache"
+	"github.com/avenga/couper/config"
+	"github.com/avenga/couper/eval"
+	"github.com/avenga/couper/handler/transport"
+	"github.com/avenga/couper/handler/validation"
+	"github.com/avenga/couper/oauth2"
+)
+
+func NewBackend(ctx *hcl.EvalContext, body hcl.Body, log *logrus.Entry,
+	settings *config.Settings, store *cache.MemoryStore) (http.RoundTripper, error) {
+	const prefix = "backend_"
+	name, err := getBackendName(ctx, body)
+
+	if err != nil {
+		return nil, err
+	}
+
+	// Making use of the store here since a global variable leads to extra efforts for integration tests.
+	// The store is newly created per run.
+	b := store.Get(prefix + name)
+	if b != nil {
+		return backend.NewContext(body, b.(http.RoundTripper)), nil
+	}
+
+	b, err = newBackend(ctx, body, log, settings, store)
+	if err != nil {
+		return nil, err
+	}
+
+	// to prevent weird debug sessions; max to set the internal memStore ttl limit.
+	store.Set(prefix+name, b, math.MaxInt64)
+
+	return b.(http.RoundTripper), nil
+}
+
+func newBackend(evalCtx *hcl.EvalContext, backendCtx hcl.Body, log *logrus.Entry,
+	settings *config.Settings, memStore *cache.MemoryStore) (http.RoundTripper, error) {
+	beConf := &config.Backend{}
+	if diags := gohcl.DecodeBody(backendCtx, evalCtx, beConf); diags.HasErrors() {
+		return nil, diags
+	}
+
+	if beConf.Name == "" {
+		name, err := getBackendName(evalCtx, backendCtx)
+		if err != nil {
+			return nil, err
+		}
+		beConf.Name = name
+	}
+
+	tc := &transport.Config{
+		BackendName:            beConf.Name,
+		Certificate:            settings.Certificate,
+		DisableCertValidation:  beConf.DisableCertValidation,
+		DisableConnectionReuse: beConf.DisableConnectionReuse,
+		HTTP2:                  beConf.HTTP2,
+		NoProxyFromEnv:         settings.NoProxyFromEnv,
+		MaxConnections:         beConf.MaxConnections,
+	}
+
+	openAPIopts, err := validation.NewOpenAPIOptions(beConf.OpenAPI)
+	if err != nil {
+		return nil, err
+	}
+
+	options := &transport.BackendOptions{
+		OpenAPI: openAPIopts,
+	}
+
+	oauthContent, _, _ := backendCtx.PartialContent(config.OAuthBlockSchema)
+	if oauthContent != nil {
+		if blocks := oauthContent.Blocks.OfType("oauth2"); len(blocks) > 0 {
+			options.AuthBackend, err = newAuthBackend(evalCtx, beConf, blocks, log, settings, memStore)
+			if err != nil {
+				return nil, err
+			}
+		}
+	}
+
+	b := transport.NewBackend(backendCtx, tc, options, log)
+	return b, nil
+}
+
+func newAuthBackend(evalCtx *hcl.EvalContext, beConf *config.Backend, blocks hcl.Blocks, log *logrus.Entry,
+	settings *config.Settings, memStore *cache.MemoryStore) (transport.TokenRequest, error) {
+
+	beConf.OAuth2 = &config.OAuth2ReqAuth{}
+
+	if diags := gohcl.DecodeBody(blocks[0].Body, evalCtx, beConf.OAuth2); diags.HasErrors() {
+		return nil, diags
+	}
+
+	innerContent, _, diags := beConf.OAuth2.Remain.PartialContent(beConf.OAuth2.Schema(true))
+	if diags.HasErrors() {
+		return nil, diags
+	}
+
+	innerBackend := innerContent.Blocks.OfType("backend")[0] // backend block is set by configload
+	authBackend, authErr := NewBackend(evalCtx, innerBackend.Body, log, settings, memStore)
+	if authErr != nil {
+		return nil, authErr
+	}
+
+	// Set default value
+	if beConf.OAuth2.Retries == nil {
+		var one uint8 = 1
+		beConf.OAuth2.Retries = &one
+	}
+
+	oauth2Client, err := oauth2.NewClientCredentialsClient(beConf.OAuth2, authBackend)
+	if err != nil {
+		return nil, err
+	}
+
+	tr, err := transport.NewOAuth2ReqAuth(beConf.OAuth2, memStore, oauth2Client)
+	return tr, err
+}
+
+func getBackendName(evalCtx *hcl.EvalContext, backendCtx hcl.Body) (string, error) {
+	content, _, _ := backendCtx.PartialContent(&hcl.BodySchema{Attributes: []hcl.AttributeSchema{
+		{Name: "name"}},
+	})
+
+	if content != nil && len(content.Attributes) > 0 {
+		if n, exist := content.Attributes["name"]; exist {
+			v, err := eval.Value(evalCtx, n.Expr)
+			if err != nil {
+				return "", err
+			}
+
+			return v.AsString(), nil
+		}
+	}
+
+	return "", nil
+}
diff --git a/config/runtime/endpoint.go b/config/runtime/endpoint.go
index 83f2692b..06842bb7 100644
--- a/config/runtime/endpoint.go
+++ b/config/runtime/endpoint.go
@@ -5,7 +5,6 @@ import (
 	"strings"
 
 	"github.com/hashicorp/hcl/v2"
-	"github.com/hashicorp/hcl/v2/gohcl"
 	"github.com/sirupsen/logrus"
 
 	"github.com/avenga/couper/cache"
@@ -62,7 +61,7 @@ func newEndpointMap(srvConf *config.Server, serverOptions *server.Options) (endp
 }
 
 func newEndpointOptions(confCtx *hcl.EvalContext, endpointConf *config.Endpoint, apiConf *config.API,
-	serverOptions *server.Options, log *logrus.Entry, proxyEnv bool, certificate []byte, memStore *cache.MemoryStore) (*handler.EndpointOptions, error) {
+	serverOptions *server.Options, log *logrus.Entry, settings *config.Settings, memStore *cache.MemoryStore) (*handler.EndpointOptions, error) {
 	var errTpl *errors.Template
 
 	if endpointConf.ErrorFile != "" {
@@ -92,7 +91,7 @@ func newEndpointOptions(confCtx *hcl.EvalContext, endpointConf *config.Endpoint,
 
 	allProxies := make(map[string]*producer.Proxy)
 	for _, proxyConf := range endpointConf.Proxies {
-		backend, innerBody, berr := newBackend(confCtx, proxyConf.Backend, log, proxyEnv, certificate, memStore)
+		backend, berr := NewBackend(confCtx, proxyConf.Backend, log, settings, memStore)
 		if berr != nil {
 			return nil, berr
 		}
@@ -103,12 +102,12 @@ func newEndpointOptions(confCtx *hcl.EvalContext, endpointConf *config.Endpoint,
 		}
 
 		allProxies[proxyConf.Name] = p
-		blockBodies = append(blockBodies, proxyConf.Backend, innerBody, proxyConf.HCLBody())
+		blockBodies = append(blockBodies, proxyConf.Backend, proxyConf.HCLBody())
 	}
 
 	allRequests := make(map[string]*producer.Request)
 	for _, requestConf := range endpointConf.Requests {
-		backend, innerBody, berr := newBackend(confCtx, requestConf.Backend, log, proxyEnv, certificate, memStore)
+		backend, berr := NewBackend(confCtx, requestConf.Backend, log, settings, memStore)
 		if berr != nil {
 			return nil, berr
 		}
@@ -120,15 +119,11 @@ func newEndpointOptions(confCtx *hcl.EvalContext, endpointConf *config.Endpoint,
 		}
 
 		allRequests[requestConf.Name] = pr
-		blockBodies = append(blockBodies, requestConf.Backend, innerBody, requestConf.HCLBody())
+		blockBodies = append(blockBodies, requestConf.Backend, requestConf.HCLBody())
 	}
 
 	sequences, requests, proxies := newSequences(allProxies, allRequests, endpointConf.Sequences...)
 
-	backendConf := *DefaultBackendConf
-	if diags := gohcl.DecodeBody(endpointConf.Remain, confCtx, &backendConf); diags.HasErrors() {
-		return nil, diags
-	}
 	// TODO: redirect
 	if endpointConf.Response == nil && len(proxies)+len(requests)+len(sequences) == 0 { // && redirect == nil
 		r := endpointConf.Remain.MissingItemRange()
diff --git a/config/runtime/error_handler.go b/config/runtime/error_handler.go
index 476b3722..e9288bc7 100644
--- a/config/runtime/error_handler.go
+++ b/config/runtime/error_handler.go
@@ -14,7 +14,7 @@ import (
 )
 
 func newErrorHandler(ctx *hcl.EvalContext, opts *protectedOptions, log *logrus.Entry,
-	defs ACDefinitions, certificate []byte, references ...string) (http.Handler, error) {
+	defs ACDefinitions, references ...string) (http.Handler, error) {
 	kindsHandler := map[string]http.Handler{}
 	for _, ref := range references {
 		definition, ok := defs[ref]
@@ -42,7 +42,7 @@ func newErrorHandler(ctx *hcl.EvalContext, opts *protectedOptions, log *logrus.E
 					epConf.Response = &config.Response{Remain: emptyBody}
 				}
 
-				epOpts, err := newEndpointOptions(ctx, epConf, nil, opts.srvOpts, log, opts.proxyFromEnv, certificate, opts.memStore)
+				epOpts, err := newEndpointOptions(ctx, epConf, nil, opts.srvOpts, log, opts.settings, opts.memStore)
 				if err != nil {
 					return nil, err
 				}
diff --git a/config/runtime/server.go b/config/runtime/server.go
index f12dbce4..420acf2b 100644
--- a/config/runtime/server.go
+++ b/config/runtime/server.go
@@ -3,7 +3,6 @@
 package runtime
 
 import (
-	"context"
 	"fmt"
 	"net"
 	"net/http"
@@ -11,11 +10,9 @@ import (
 	"reflect"
 	"strconv"
 	"strings"
-	"time"
 
 	"github.com/docker/go-units"
 	"github.com/hashicorp/hcl/v2"
-	"github.com/hashicorp/hcl/v2/gohcl"
 	"github.com/sirupsen/logrus"
 
 	ac "github.com/avenga/couper/accesscontrol"
@@ -30,8 +27,6 @@ import (
 	"github.com/avenga/couper/eval"
 	"github.com/avenga/couper/handler"
 	"github.com/avenga/couper/handler/middleware"
-	"github.com/avenga/couper/handler/transport"
-	"github.com/avenga/couper/handler/validation"
 	"github.com/avenga/couper/internal/seetie"
 	"github.com/avenga/couper/oauth2"
 	"github.com/avenga/couper/oauth2/oidc"
@@ -45,12 +40,6 @@ const (
 	spa
 )
 
-var DefaultBackendConf = &config.Backend{
-	ConnectTimeout: "10s",
-	TTFBTimeout:    "60s",
-	Timeout:        "300s",
-}
-
 type (
 	Port                int
 	Hosts               map[string]*MuxOptions
@@ -113,7 +102,7 @@ func NewServerConfiguration(conf *config.Couper, log *logrus.Entry, memStore *ca
 	if ocErr != nil {
 		return nil, ocErr
 	}
-	evalContext.WithOidcConfig(oidcConfigs)
+	conf.Context = evalContext.WithOidcConfig(oidcConfigs)
 
 	accessControls, acErr := configureAccessControls(conf, confCtx, log, memStore, oidcConfigs)
 	if acErr != nil {
@@ -127,6 +116,16 @@ func NewServerConfiguration(conf *config.Couper, log *logrus.Entry, memStore *ca
 		isHostsMandatory    = len(conf.Servers) > 1
 	)
 
+	// Populate defined backends first...
+	if conf.Definitions != nil {
+		for _, backend := range conf.Definitions.Backend {
+			_, err := NewBackend(confCtx, backend.HCLBody(), log, conf.Settings, memStore)
+			if err != nil {
+				return nil, err
+			}
+		}
+	}
+
 	for _, srvConf := range conf.Servers {
 		serverOptions, err := server.NewServerOptions(srvConf, log)
 		if err != nil {
@@ -176,12 +175,12 @@ func NewServerConfiguration(conf *config.Couper, log *logrus.Entry, memStore *ca
 				config.NewAccessControl(srvConf.AccessControl, srvConf.DisableAccessControl),
 				config.NewAccessControl(srvConf.Spa.AccessControl, srvConf.Spa.DisableAccessControl),
 				&protectedOptions{
-					epOpts:       epOpts,
-					handler:      spaHandler,
-					memStore:     memStore,
-					proxyFromEnv: conf.Settings.NoProxyFromEnv,
-					srvOpts:      serverOptions,
-				}, conf.Settings.Certificate, log)
+					epOpts:   epOpts,
+					handler:  spaHandler,
+					memStore: memStore,
+					settings: conf.Settings,
+					srvOpts:  serverOptions,
+				}, log)
 			if err != nil {
 				return nil, err
 			}
@@ -225,12 +224,12 @@ func NewServerConfiguration(conf *config.Couper, log *logrus.Entry, memStore *ca
 				config.NewAccessControl(srvConf.AccessControl, srvConf.DisableAccessControl),
 				config.NewAccessControl(srvConf.Files.AccessControl, srvConf.Files.DisableAccessControl),
 				&protectedOptions{
-					epOpts:       epOpts,
-					handler:      fileHandler,
-					memStore:     memStore,
-					proxyFromEnv: conf.Settings.NoProxyFromEnv,
-					srvOpts:      serverOptions,
-				}, conf.Settings.Certificate, log)
+					epOpts:   epOpts,
+					handler:  fileHandler,
+					memStore: memStore,
+					settings: conf.Settings,
+					srvOpts:  serverOptions,
+				}, log)
 			if err != nil {
 				return nil, err
 			}
@@ -276,10 +275,8 @@ func NewServerConfiguration(conf *config.Couper, log *logrus.Entry, memStore *ca
 			}
 			endpointPatterns[cleanPattern] = true
 
-			epOpts, err := newEndpointOptions(
-				confCtx, endpointConf, parentAPI, serverOptions,
-				log, conf.Settings.NoProxyFromEnv, conf.Settings.Certificate, memStore,
-			)
+			epOpts, err := newEndpointOptions(confCtx, endpointConf, parentAPI, serverOptions,
+				log, conf.Settings, memStore)
 			if err != nil {
 				return nil, err
 			}
@@ -312,11 +309,11 @@ func NewServerConfiguration(conf *config.Couper, log *logrus.Entry, memStore *ca
 				protectedHandler = epOpts.ErrorTemplate.WithError(errors.RouteNotFound)
 			} else {
 				epErrorHandler, err := newErrorHandler(confCtx, &protectedOptions{
-					epOpts:       epOpts,
-					memStore:     memStore,
-					proxyFromEnv: conf.Settings.NoProxyFromEnv,
-					srvOpts:      serverOptions,
-				}, log, errorHandlerDefinitions, conf.Settings.Certificate, "api", "endpoint")
+					epOpts:   epOpts,
+					memStore: memStore,
+					settings: conf.Settings,
+					srvOpts:  serverOptions,
+				}, log, errorHandlerDefinitions, "api", "endpoint")
 				if err != nil {
 					return nil, err
 				}
@@ -332,11 +329,11 @@ func NewServerConfiguration(conf *config.Couper, log *logrus.Entry, memStore *ca
 
 				scopeControl := ac.NewScopeControl(scopeMaps)
 				scopeErrorHandler, err := newErrorHandler(confCtx, &protectedOptions{
-					epOpts:       epOpts,
-					memStore:     memStore,
-					proxyFromEnv: conf.Settings.NoProxyFromEnv,
-					srvOpts:      serverOptions,
-				}, log, errorHandlerDefinitions, conf.Settings.Certificate, "api", "endpoint")
+					epOpts:   epOpts,
+					memStore: memStore,
+					settings: conf.Settings,
+					srvOpts:  serverOptions,
+				}, log, errorHandlerDefinitions, "api", "endpoint")
 				if err != nil {
 					return nil, err
 				}
@@ -358,12 +355,12 @@ func NewServerConfiguration(conf *config.Couper, log *logrus.Entry, memStore *ca
 			epHandler, err = configureProtectedHandler(accessControls, confCtx, accessControl,
 				config.NewAccessControl(endpointConf.AccessControl, endpointConf.DisableAccessControl),
 				&protectedOptions{
-					epOpts:       epOpts,
-					handler:      protectedHandler,
-					memStore:     memStore,
-					proxyFromEnv: conf.Settings.NoProxyFromEnv,
-					srvOpts:      serverOptions,
-				}, conf.Settings.Certificate, log)
+					epOpts:   epOpts,
+					handler:  protectedHandler,
+					memStore: memStore,
+					settings: conf.Settings,
+					srvOpts:  serverOptions,
+				}, log)
 			if err != nil {
 				return nil, err
 			}
@@ -452,117 +449,6 @@ func newScopeMaps(parentAPI *config.API, endpoint *config.Endpoint) ([]map[strin
 	return scopeMaps, nil
 }
 
-func newBackend(evalCtx *hcl.EvalContext, backendCtx hcl.Body, log *logrus.Entry,
-	ignoreProxyEnv bool, certificate []byte, memStore *cache.MemoryStore) (http.RoundTripper, hcl.Body, error) {
-	beConf := *DefaultBackendConf
-	if diags := gohcl.DecodeBody(backendCtx, evalCtx, &beConf); diags.HasErrors() {
-		return nil, nil, diags
-	}
-
-	if beConf.Name == "" {
-		name, err := getBackendName(evalCtx, backendCtx)
-		if err != nil {
-			return nil, nil, err
-		}
-		beConf.Name = name
-	}
-
-	tc := &transport.Config{
-		BackendName:            beConf.Name,
-		Certificate:            certificate,
-		DisableCertValidation:  beConf.DisableCertValidation,
-		DisableConnectionReuse: beConf.DisableConnectionReuse,
-		HTTP2:                  beConf.HTTP2,
-		MaxConnections:         beConf.MaxConnections,
-		NoProxyFromEnv:         ignoreProxyEnv,
-	}
-
-	if err := parseDuration(beConf.ConnectTimeout, &tc.ConnectTimeout); err != nil {
-		return nil, nil, err
-	}
-
-	if err := parseDuration(beConf.TTFBTimeout, &tc.TTFBTimeout); err != nil {
-		return nil, nil, err
-	}
-
-	if err := parseDuration(beConf.Timeout, &tc.Timeout); err != nil {
-		return nil, nil, err
-	}
-
-	openAPIopts, err := validation.NewOpenAPIOptions(beConf.OpenAPI)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	options := &transport.BackendOptions{
-		OpenAPI: openAPIopts,
-	}
-	backend := transport.NewBackend(backendCtx, tc, options, log)
-
-	oauthContent, _, _ := backendCtx.PartialContent(config.OAuthBlockSchema)
-	if oauthContent == nil {
-		return backend, backendCtx, nil
-	}
-
-	if blocks := oauthContent.Blocks.OfType("oauth2"); len(blocks) > 0 {
-		return newAuthBackend(evalCtx, beConf, blocks, log, ignoreProxyEnv, certificate, memStore, backend)
-	}
-
-	return backend, backendCtx, nil
-}
-
-func newAuthBackend(evalCtx *hcl.EvalContext, beConf config.Backend, blocks hcl.Blocks, log *logrus.Entry,
-	ignoreProxyEnv bool, certificate []byte, memStore *cache.MemoryStore, backend http.RoundTripper) (http.RoundTripper, hcl.Body, error) {
-
-	beConf.OAuth2 = &config.OAuth2ReqAuth{}
-
-	if diags := gohcl.DecodeBody(blocks[0].Body, evalCtx, beConf.OAuth2); diags.HasErrors() {
-		return nil, nil, diags
-	}
-
-	innerContent, _, diags := beConf.OAuth2.Remain.PartialContent(beConf.OAuth2.Schema(true))
-	if diags.HasErrors() {
-		return nil, nil, diags
-	}
-
-	innerBackend := innerContent.Blocks.OfType("backend")[0] // backend block is set by configload
-	authBackend, body, authErr := newBackend(evalCtx, innerBackend.Body, log, ignoreProxyEnv, certificate, memStore)
-	if authErr != nil {
-		return nil, nil, authErr
-	}
-
-	// Set default value
-	if beConf.OAuth2.Retries == nil {
-		var one uint8 = 1
-		beConf.OAuth2.Retries = &one
-	}
-
-	oauth2Client, err := oauth2.NewOAuth2CC(beConf.OAuth2, authBackend)
-	if err != nil {
-		return nil, body, err
-	}
-
-	rt, err := transport.NewOAuth2ReqAuth(beConf.OAuth2, memStore, oauth2Client, backend)
-	return rt, body, err
-}
-
-func getBackendName(evalCtx *hcl.EvalContext, backendCtx hcl.Body) (string, error) {
-	content, _, _ := backendCtx.PartialContent(&hcl.BodySchema{Attributes: []hcl.AttributeSchema{
-		{Name: "name"}},
-	})
-	if content != nil && len(content.Attributes) > 0 {
-
-		if n, exist := content.Attributes["name"]; exist {
-			v, err := eval.Value(evalCtx, n.Expr)
-			if err != nil {
-				return "", err
-			}
-			return v.AsString(), nil
-		}
-	}
-	return "", nil
-}
-
 func whichCORS(parent *config.Server, this interface{}) *config.CORS {
 	val := reflect.ValueOf(this)
 	if val.IsZero() {
@@ -587,27 +473,16 @@ func configureOidcConfigs(conf *config.Couper, confCtx *hcl.EvalContext, log *lo
 	if conf.Definitions != nil {
 		for _, oidcConf := range conf.Definitions.OIDC {
 			confErr := errors.Configuration.Label(oidcConf.Name)
-			backend, _, err := newBackend(confCtx, oidcConf.Backend, log, conf.Settings.NoProxyFromEnv, conf.Settings.Certificate, memStore)
-			if err != nil {
-				return nil, confErr.With(err)
-			}
-
-			oidcConfig, err := oidc.NewConfig(oidcConf, backend)
-			if err != nil {
-				return nil, confErr.With(err)
-			}
-
-			oidcConfigs[oidcConf.Name] = oidcConfig
-		}
-		// TODO remove for version 1.8
-		for _, oidcConf := range conf.Definitions.BetaOIDC {
-			confErr := errors.Configuration.Label(oidcConf.Name)
-			backend, _, err := newBackend(confCtx, oidcConf.Backend, log, conf.Settings.NoProxyFromEnv, conf.Settings.Certificate, memStore)
-			if err != nil {
-				return nil, confErr.With(err)
+			backends := map[string]http.RoundTripper{}
+			for k, backendBody := range oidcConf.Backends {
+				var err error
+				backends[k], err = NewBackend(confCtx, backendBody, log, conf.Settings, memStore)
+				if err != nil {
+					return nil, confErr.With(err)
+				}
 			}
 
-			oidcConfig, err := oidc.NewConfig(oidcConf, backend)
+			oidcConfig, err := oidc.NewConfig(oidcConf, backends)
 			if err != nil {
 				return nil, confErr.With(err)
 			}
@@ -669,12 +544,12 @@ func configureAccessControls(conf *config.Couper, confCtx *hcl.EvalContext, log
 
 		for _, oauth2Conf := range conf.Definitions.OAuth2AC {
 			confErr := errors.Configuration.Label(oauth2Conf.Name)
-			backend, _, err := newBackend(confCtx, oauth2Conf.Backend, log, conf.Settings.NoProxyFromEnv, conf.Settings.Certificate, memStore)
+			backend, err := NewBackend(confCtx, oauth2Conf.Backend, log, conf.Settings, memStore)
 			if err != nil {
 				return nil, confErr.With(err)
 			}
 
-			oauth2Client, err := oauth2.NewOAuth2AC(oauth2Conf, oauth2Conf, backend)
+			oauth2Client, err := oauth2.NewAuthCodeClient(oauth2Conf, oauth2Conf, backend)
 			if err != nil {
 				return nil, confErr.With(err)
 			}
@@ -692,45 +567,11 @@ func configureAccessControls(conf *config.Couper, confCtx *hcl.EvalContext, log
 		for _, oidcConf := range conf.Definitions.OIDC {
 			confErr := errors.Configuration.Label(oidcConf.Name)
 			oidcConfig := oidcConfigs[oidcConf.Name]
-			oidcClient, err := oauth2.NewOidc(oidcConfig)
-			if err != nil {
-				return nil, confErr.With(err)
-			}
-
-			if oidcConfig.VerifierMethod != "" &&
-				oidcConfig.VerifierMethod != config.CcmS256 &&
-				oidcConfig.VerifierMethod != "nonce" {
-				return nil, errors.Configuration.
-					Label(oidcConf.Name).
-					Messagef("verifier_method %s not supported", oidcConfig.VerifierMethod)
-			}
-
-			oa, err := ac.NewOAuth2Callback(oidcClient)
-			if err != nil {
-				return nil, confErr.With(err)
-			}
-
-			if err = accessControls.Add(oidcConf.Name, oa, oidcConf.ErrorHandler); err != nil {
-				return nil, confErr.With(err)
-			}
-		}
-		// TODO remove for version 1.8
-		for _, oidcConf := range conf.Definitions.BetaOIDC {
-			confErr := errors.Configuration.Label(oidcConf.Name)
-			oidcConfig := oidcConfigs[oidcConf.Name]
-			oidcClient, err := oauth2.NewOidc(oidcConfig)
+			oidcClient, err := oauth2.NewOidcClient(oidcConfig)
 			if err != nil {
 				return nil, confErr.With(err)
 			}
 
-			if oidcConfig.VerifierMethod != "" &&
-				oidcConfig.VerifierMethod != config.CcmS256 &&
-				oidcConfig.VerifierMethod != "nonce" {
-				return nil, errors.Configuration.
-					Label(oidcConf.Name).
-					Messagef("verifier_method %s not supported", oidcConfig.VerifierMethod)
-			}
-
 			oa, err := ac.NewOAuth2Callback(oidcClient)
 			if err != nil {
 				return nil, confErr.With(err)
@@ -763,8 +604,7 @@ func newJWT(jwtConf *config.JWT, conf *config.Couper, confCtx *hcl.EvalContext,
 		err error
 	)
 	if jwtConf.JWKsURL != "" {
-		noProxy := conf.Settings.NoProxyFromEnv
-		jwks, jerr := configureJWKS(jwtConf, conf, confCtx, log, noProxy, memStore)
+		jwks, jerr := configureJWKS(jwtConf, confCtx, log, conf, memStore)
 		if jerr != nil {
 			return nil, jerr
 		}
@@ -788,42 +628,39 @@ func newJWT(jwtConf *config.JWT, conf *config.Couper, confCtx *hcl.EvalContext,
 	return jwt, nil
 }
 
-func configureJWKS(jwtConf *config.JWT, conf *config.Couper, confContext *hcl.EvalContext, log *logrus.Entry, ignoreProxyEnv bool, memStore *cache.MemoryStore) (*jwk.JWKS, error) {
+func configureJWKS(jwtConf *config.JWT, confContext *hcl.EvalContext, log *logrus.Entry, conf *config.Couper, memStore *cache.MemoryStore) (*jwk.JWKS, error) {
 	var backend http.RoundTripper
 
-	if jwtConf.Backend != nil {
-		b, _, err := newBackend(confContext, jwtConf.Backend, log, ignoreProxyEnv, conf.Settings.Certificate, memStore)
-		if err != nil {
-			return nil, err
+	if jwtConf.Backends != nil {
+		backendBody, ok := jwtConf.Backends["backend"]
+		if ok {
+			b, err := NewBackend(confContext, backendBody, log, conf.Settings, memStore)
+			if err != nil {
+				return nil, err
+			}
+			backend = b
 		}
-		backend = b
 	}
 
-	evalContext := conf.Context.Value(request.ContextType).(context.Context)
-	jwks, err := jwk.NewJWKS(jwtConf.JWKsURL, jwtConf.JWKsTTL, backend, evalContext)
-	if err != nil {
-		return nil, err
-	}
-
-	return jwks, nil
+	return jwk.NewJWKS(jwtConf.JWKsURL, jwtConf.JWKsTTL, backend)
 }
 
 type protectedOptions struct {
-	epOpts       *handler.EndpointOptions
-	handler      http.Handler
-	proxyFromEnv bool
-	memStore     *cache.MemoryStore
-	srvOpts      *server.Options
+	epOpts   *handler.EndpointOptions
+	handler  http.Handler
+	memStore *cache.MemoryStore
+	settings *config.Settings
+	srvOpts  *server.Options
 }
 
 func configureProtectedHandler(m ACDefinitions, ctx *hcl.EvalContext, parentAC, handlerAC config.AccessControl,
-	opts *protectedOptions, certificate []byte, log *logrus.Entry) (http.Handler, error) {
+	opts *protectedOptions, log *logrus.Entry) (http.Handler, error) {
 	var list ac.List
 	for _, acName := range parentAC.Merge(handlerAC).List() {
 		if e := m.MustExist(acName); e != nil {
 			return nil, e
 		}
-		eh, err := newErrorHandler(ctx, opts, log, m, certificate, acName)
+		eh, err := newErrorHandler(ctx, opts, log, m, acName)
 		if err != nil {
 			return nil, err
 		}
@@ -906,16 +743,6 @@ func getPortsHostsList(hosts []string, defaultPort int) (Ports, error) {
 	return portsHosts, nil
 }
 
-// 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
-	}
-	*target = d
-	return nil
-}
-
 func parseBodyLimit(limit string) (int64, error) {
 	const defaultReqBodyLimit = "64MiB"
 	requestBodyLimit := defaultReqBodyLimit
diff --git a/config/sequence.go b/config/sequence.go
index 8e48989e..22fd20e7 100644
--- a/config/sequence.go
+++ b/config/sequence.go
@@ -10,7 +10,7 @@ import (
 type Sequences []*Sequence
 
 type Sequence struct {
-	BodyRange hcl.Range
+	BodyRange *hcl.Range
 	Name      string
 	deps      Sequences
 	parent    *Sequence
@@ -41,7 +41,7 @@ func (s *Sequence) Add(ref *Sequence) {
 			Detail: fmt.Sprintf("circular sequence reference: %s",
 				strings.Join(append(refs, refs[0]), ",")),
 			Severity: hcl.DiagError,
-			Subject:  &s.BodyRange,
+			Subject:  s.BodyRange,
 			Summary:  "configuration error",
 		}
 		panic(err)
diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md
index f14b6770..541e6856 100644
--- a/docs/REFERENCE.md
+++ b/docs/REFERENCE.md
@@ -468,23 +468,31 @@ The `oidc` block lets you configure the `oauth2_authorization_url()` [function](
 control for an OIDC **Authorization Code Grant Flow** redirect endpoint.
 Like all [Access Control](#access-control) types, the `oidc` block is defined in the [Definitions Block](#definitions-block) and can be referenced in all configuration blocks by its required _label_.
 
-|Block name|Context|Label|Nested block(s)|
-| :-----------| :-----------| :-----------| :-----------|
-|`oidc`| [Definitions Block](#definitions-block)| &#9888; required | [Backend Block](#backend-block), [Error Handler Block(s)](ERRORS.md#error_handler-specification) |
-
-| Attribute(s) | Type |Default|Description|Characteristic(s)| Example|
-| :------------------------------ | :--------------- | :--------------- | :--------------- | :--------------- | :--------------- |
-| `backend`                       |string|-|[Backend Block Reference](#backend-block)| &#9888; Do not disable the peer certificate validation with `disable_certificate_validation = true`! |-|
-| `configuration_url` | string |-| The OpenID configuration URL. |&#9888; required|-|
-| `configuration_ttl` | [duration](#duration) | `1h` | The duration to cache the OpenID configuration located at `configuration_url`. | - | `configuration_ttl = "1d"` |
-| `token_endpoint_auth_method` |string|`client_secret_basic`|Defines the method to authenticate the client at the token endpoint.|If set to `client_secret_post`, the client credentials are transported in the request body. If set to `client_secret_basic`, the client credentials are transported via Basic Authentication.|-|
-| `redirect_uri` | string |-| The Couper endpoint for receiving the authorization code. |&#9888; required. Relative URL references are resolved against the origin of the current request URL. The origin can be changed with the [`accept_forwarded_url`](#settings-block) attribute if Couper is running behind a proxy. |-|
-| `client_id`|  string|-|The client identifier.|&#9888; required|-|
-| `client_secret` |string|-|The client password.|&#9888; required.|-|
-| `scope` |string|-| A space separated list of requested scopes for the access token.|`openid` is automatically added.| `scope = "profile read"` |
-| `verifier_method` | string | - | The method to verify the integrity of the authorization code flow | available values: `ccm_s256` (`code_challenge` parameter with `code_challenge_method` `S256`), `nonce` (`nonce` parameter) | `verifier_method = "nonce"` |
-| `verifier_value` | string or expression | - | The value of the (unhashed) verifier. | &#9888; required; e.g. using cookie value created with [`oauth2_verifier()` function](#functions) | `verifier_value = request.cookies.verifier` |
-| `custom_log_fields` | map | - | Defines log fields for [Custom Logging](LOGS.md#custom-logging). | &#9888; Inherited by nested blocks. | - |
+| Block name | Context                                 | Label            | Nested block(s)                                                                                  |
+|:-----------|:----------------------------------------|:-----------------|:-------------------------------------------------------------------------------------------------|
+| `oidc`     | [Definitions Block](#definitions-block) | &#9888; required | [Backend Block](#backend-block), [Error Handler Block(s)](ERRORS.md#error_handler-specification) |
+
+
+| Attribute(s)                 | Type                  | Default               | Description                                                                    | Characteristic(s)                                                                                                                                                                                                                 | Example                                     |
+|:-----------------------------|:----------------------|:----------------------|:-------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------|
+| `backend`                    | string                | -                     | [Backend Block Reference](#backend-block)                                      | &#9888; Do not disable the peer certificate validation with `disable_certificate_validation = true`!                                                                                                                              | -                                           |
+| `configuration_url`          | string                | -                     | The OpenID configuration URL.                                                  | &#9888; required                                                                                                                                                                                                                  | -                                           |
+| `configuration_ttl`          | [duration](#duration) | `1h`                  | The duration to cache the OpenID configuration located at `configuration_url`. | -                                                                                                                                                                                                                                 | `configuration_ttl = "1d"`                  |
+| `token_endpoint_auth_method` | string                | `client_secret_basic` | Defines the method to authenticate the client at the token endpoint.           | If set to `client_secret_post`, the client credentials are transported in the request body. If set to `client_secret_basic`, the client credentials are transported via Basic Authentication.                                     | -                                           |
+| `redirect_uri`               | string                | -                     | The Couper endpoint for receiving the authorization code.                      | &#9888; required. Relative URL references are resolved against the origin of the current request URL. The origin can be changed with the [`accept_forwarded_url`](#settings-block) attribute if Couper is running behind a proxy. | -                                           |
+| `client_id`                  | string                | -                     | The client identifier.                                                         | &#9888; required                                                                                                                                                                                                                  | -                                           |
+| `client_secret`              | string                | -                     | The client password.                                                           | &#9888; required.                                                                                                                                                                                                                 | -                                           |
+| `scope`                      | string                | -                     | A space separated list of requested scopes for the access token.               | `openid` is automatically added.                                                                                                                                                                                                  | `scope = "profile read"`                    |
+| `verifier_method`            | string                | -                     | The method to verify the integrity of the authorization code flow              | available values: `ccm_s256` (`code_challenge` parameter with `code_challenge_method` `S256`), `nonce` (`nonce` parameter)                                                                                                        | `verifier_method = "nonce"`                 |
+| `verifier_value`             | string or expression  | -                     | The value of the (unhashed) verifier.                                          | &#9888; required; e.g. using cookie value created with [`oauth2_verifier()` function](#functions)                                                                                                                                 | `verifier_value = request.cookies.verifier` |
+| `custom_log_fields`          | map                   | -                     | Defines log fields for [Custom Logging](LOGS.md#custom-logging).               | &#9888; Inherited by nested blocks.                                                                                                                                                                                               | -                                           |
+| `configuration_backend`      | string                | -                     | [Backend Block Reference](#backend-block)                                      | &#9888; Do not disable the peer certificate validation with `disable_certificate_validation = true`!                                                                                                                              | -                                           |
+| `jwks_uri_backend`           | string                | -                     | [Backend Block Reference](#backend-block)                                      | &#9888; Do not disable the peer certificate validation with `disable_certificate_validation = true`!                                                                                                                              | -                                           |
+| `token_backend`              | string                | -                     | [Backend Block Reference](#backend-block)                                      | &#9888; Do not disable the peer certificate validation with `disable_certificate_validation = true`!                                                                                                                              | -                                           |
+| `userinfo_backend`           | string                | -                     | [Backend Block Reference](#backend-block)                                      | &#9888; Do not disable the peer certificate validation with `disable_certificate_validation = true`!                                                                                                                              | -                                           |
+
+In most cases, referencing one `backend` (backend attribute) for all the backend requests sent by the OIDC client is enough.
+You should only use `configuration_backend`, `jwks_uri_backend`, `token_backend` or `userinfo_backend` if you need to configure a specific behaviour for the respective request (e.g. timeouts).
 
 If the OpenID server supports the `code_challenge_method` `S256` the default value for `verifier_method`is `ccm_s256`, `nonce` otherwise.
 
diff --git a/handler/transport/backend.go b/handler/transport/backend.go
index d5ec1c85..7396b583 100644
--- a/handler/transport/backend.go
+++ b/handler/transport/backend.go
@@ -6,8 +6,10 @@ import (
 	"encoding/base64"
 	"io"
 	"net/http"
+	"net/http/httptrace"
 	"net/url"
 	"strings"
+	"sync"
 	"time"
 
 	"github.com/hashicorp/hcl/v2"
@@ -20,6 +22,7 @@ import (
 	"go.opentelemetry.io/otel/trace"
 
 	"github.com/avenga/couper/config"
+	hclbody "github.com/avenga/couper/config/body"
 	"github.com/avenga/couper/config/request"
 	"github.com/avenga/couper/errors"
 	"github.com/avenga/couper/eval"
@@ -41,10 +44,11 @@ type Backend struct {
 	logEntry         *logrus.Entry
 	name             string
 	openAPIValidator *validation.OpenAPI
-	options          *BackendOptions
+	tokenRequest     TokenRequest
+	transport        *http.Transport
+	transportOnce    sync.Once
 	transportConf    *Config
 	upstreamLog      *logging.UpstreamLog
-	// TODO: OrderedList for origin AC, middlewares etc.
 }
 
 // NewBackend creates a new <*Backend> object by the given <*Config>.
@@ -58,46 +62,73 @@ func NewBackend(ctx hcl.Body, tc *Config, opts *BackendOptions, log *logrus.Entr
 	}
 
 	var openAPI *validation.OpenAPI
+	var tr TokenRequest
 	if opts != nil {
 		openAPI = validation.NewOpenAPI(opts.OpenAPI)
+		tr = opts.AuthBackend
 	}
 
 	backend := &Backend{
 		context:          ctx,
 		logEntry:         logEntry,
+		name:             tc.BackendName,
 		openAPIValidator: openAPI,
-		options:          opts,
+		tokenRequest:     tr,
 		transportConf:    tc,
 	}
 	backend.upstreamLog = logging.NewUpstreamLog(logEntry, backend, tc.NoProxyFromEnv)
 	return backend.upstreamLog
 }
 
+// initOnce ensures synced transport configuration. First request will setup the origin, hostname and tls.
+func (b *Backend) initOnce(conf *Config) {
+	b.transport = NewTransport(conf, b.logEntry)
+}
+
 // RoundTrip implements the <http.RoundTripper> interface.
 func (b *Backend) RoundTrip(req *http.Request) (*http.Response, error) {
-	ctx := context.WithValue(req.Context(), request.LogCustomUpstream, []hcl.Body{b.context})
-	*req = *req.WithContext(ctx)
+	// for token-request retry purposes
+	originalReq := req.Clone(req.Context())
+
+	if err := b.withTokenRequest(req); err != nil {
+		return nil, err
+	}
+
+	ctxBody, _ := req.Context().Value(request.BackendParams).(hcl.Body)
+	if ctxBody == nil {
+		ctxBody = b.context
+	} else {
+		ctxBody = hclbody.MergeBodies(b.context, ctxBody)
+	}
+
+	logCh, _ := req.Context().Value(request.LogCustomUpstream).(chan hcl.Body)
+	if logCh != nil {
+		logCh <- ctxBody
+	}
 
 	hclCtx := eval.ContextFromRequest(req).HCLContextSync()
 
 	// Execute before <b.evalTransport()> due to right
 	// handling of query-params in the URL attribute.
-	if err := eval.ApplyRequestContext(hclCtx, b.context, req); err != nil {
+	if err := eval.ApplyRequestContext(hclCtx, ctxBody, req); err != nil {
 		return nil, err
 	}
 
-	tc, err := b.evalTransport(hclCtx, req)
+	tc, err := b.evalTransport(hclCtx, ctxBody, req)
 	if err != nil {
 		return nil, err
 	}
+	b.transportOnce.Do(func() {
+		b.initOnce(tc)
+	})
 
-	deadlineErr := b.withTimeout(req)
+	deadlineErr := b.withTimeout(req, tc)
 
 	req.URL.Host = tc.Origin
 	req.URL.Scheme = tc.Scheme
 	req.Host = tc.Hostname
 
-	// handler.Proxy marks proxy roundtrips since we should not handle headers twice.
+	// handler.Proxy marks proxy round-trips since we should not handle headers twice.
 	_, isProxyReq := req.Context().Value(request.RoundTripProxy).(bool)
 
 	if !isProxyReq {
@@ -115,8 +146,8 @@ func (b *Backend) RoundTrip(req *http.Request) (*http.Response, error) {
 		}
 	}
 
-	b.withBasicAuth(req)
-	if err = b.withPathPrefix(req); err != nil {
+	b.withBasicAuth(req, ctxBody)
+	if err = b.withPathPrefix(req, ctxBody); err != nil {
 		return nil, err
 	}
 
@@ -145,6 +176,12 @@ func (b *Backend) RoundTrip(req *http.Request) (*http.Response, error) {
 		return berespErr, err
 	}
 
+	if retry, rerr := b.withRetryTokenRequest(req, beresp); rerr != nil {
+		return beresp, rerr
+	} else if retry {
+		return b.RoundTrip(originalReq)
+	}
+
 	if !eval.IsUpgradeResponse(req, beresp) {
 		if err = setGzipReader(beresp); err != nil {
 			b.upstreamLog.LogEntry().WithContext(req.Context()).WithError(err).Error()
@@ -163,7 +200,7 @@ func (b *Backend) RoundTrip(req *http.Request) (*http.Response, error) {
 	// has own body variable reference?
 	readBody := eval.MustBuffer(b.context)&eval.BufferResponse == eval.BufferResponse
 	evalCtx = evalCtx.WithBeresp(beresp, readBody)
-	err = eval.ApplyResponseContext(evalCtx.HCLContext(), b.context, beresp)
+	err = eval.ApplyResponseContext(evalCtx.HCLContext(), ctxBody, beresp)
 
 	if varSync, ok := req.Context().Value(request.ContextVariablesSynced).(*eval.SyncedVariables); ok {
 		varSync.Set(beresp)
@@ -173,7 +210,7 @@ func (b *Backend) RoundTrip(req *http.Request) (*http.Response, error) {
 }
 
 func (b *Backend) openAPIValidate(req *http.Request, tc *Config, deadlineErr <-chan error) (*http.Response, error) {
-	requestValidationInput, err := b.openAPIValidator.ValidateRequest(req, tc.hash())
+	requestValidationInput, err := b.openAPIValidator.ValidateRequest(req)
 	if err != nil {
 		return nil, errors.BackendValidation.Label(b.name).Kind("backend_request_validation").With(err)
 	}
@@ -212,10 +249,9 @@ func (b *Backend) innerRoundTrip(req *http.Request, tc *Config, deadlineErr <-ch
 		attribute.String("origin", tc.Origin),
 	}
 
-	t := Get(tc, b.logEntry)
 	start := time.Now()
 	span.AddEvent(spanMsg + ".request")
-	beresp, err := t.RoundTrip(req)
+	beresp, err := b.transport.RoundTrip(req)
 	span.AddEvent(spanMsg + ".response")
 	endSeconds := time.Since(start).Seconds()
 
@@ -243,13 +279,48 @@ func (b *Backend) innerRoundTrip(req *http.Request, tc *Config, deadlineErr <-ch
 	return beresp, nil
 }
 
-func (b *Backend) withPathPrefix(req *http.Request) error {
-	if pathPrefix := b.getAttribute(req, "path_prefix"); pathPrefix != "" {
+const backendTokenRequest = "backendTokenRequest"
+
+func (b *Backend) withTokenRequest(req *http.Request) error {
+	if b.tokenRequest == nil {
+		return nil
+	}
+
+	trValue, _ := req.Context().Value(backendTokenRequest).(string)
+	if trValue != "" { // prevent loop
+		return nil
+	}
+
+	ctx := context.WithValue(req.Context(), backendTokenRequest, "tr")
+	// Reset for upstream transport; prevent mixing values.
+	// tokenRequest will have their own backend configuration.
+	ctx = context.WithValue(ctx, request.BackendParams, nil)
+	ctx = context.WithValue(ctx, request.URLAttribute, "")
+
+	// WithContext() instead of Clone() due to header-map modification.
+	return b.tokenRequest.WithToken(req.WithContext(ctx))
+}
+
+func (b *Backend) withRetryTokenRequest(req *http.Request, res *http.Response) (bool, error) {
+	if b.tokenRequest == nil {
+		return false, nil
+	}
+
+	trValue, _ := req.Context().Value(backendTokenRequest).(string)
+	if trValue != "" { // prevent loop
+		return false, nil
+	}
+
+	return b.tokenRequest.RetryWithToken(req, res)
+}
+
+func (b *Backend) withPathPrefix(req *http.Request, hclContext hcl.Body) error {
+	if pathPrefix := b.getAttribute(req, "path_prefix", hclContext); pathPrefix != "" {
 		// TODO: Check for a valid absolute path
 		if i := strings.Index(pathPrefix, "#"); i >= 0 {
-			return errors.Configuration.Messagef("path_prefix attribute: invalid fragment found in \"%s\"", pathPrefix)
-		} else if i := strings.Index(pathPrefix, "?"); i >= 0 {
-			return errors.Configuration.Messagef("path_prefix attribute: invalid query string found in \"%s\"", pathPrefix)
+			return errors.Configuration.Messagef("path_prefix attribute: invalid fragment found in %q", pathPrefix)
+		} else if i = strings.Index(pathPrefix, "?"); i >= 0 {
+			return errors.Configuration.Messagef("path_prefix attribute: invalid query string found in %q", pathPrefix)
 		}
 
 		req.URL.Path = utils.JoinPath("/", pathPrefix, req.URL.Path)
@@ -258,23 +329,23 @@ func (b *Backend) withPathPrefix(req *http.Request) error {
 	return nil
 }
 
-func (b *Backend) withBasicAuth(req *http.Request) {
-	if creds := b.getAttribute(req, "basic_auth"); creds != "" {
+func (b *Backend) withBasicAuth(req *http.Request, hclContext hcl.Body) {
+	if creds := b.getAttribute(req, "basic_auth", hclContext); creds != "" {
 		auth := base64.StdEncoding.EncodeToString([]byte(creds))
 		req.Header.Set("Authorization", "Basic "+auth)
 	}
 }
 
-func (b *Backend) getAttribute(req *http.Request, name string) string {
-	attrVal, err := eval.ValueFromBodyAttribute(eval.ContextFromRequest(req).HCLContext(), b.context, name)
+func (b *Backend) getAttribute(req *http.Request, name string, hclContext hcl.Body) string {
+	attrVal, err := eval.ValueFromBodyAttribute(eval.ContextFromRequest(req).HCLContext(), hclContext, name)
 	if err != nil {
 		b.upstreamLog.LogEntry().WithError(errors.Evaluation.Label(b.name).With(err))
 	}
 	return seetie.ValueToString(attrVal)
 }
 
-func (b *Backend) withTimeout(req *http.Request) <-chan error {
-	timeout := b.transportConf.Timeout
+func (b *Backend) withTimeout(req *http.Request, conf *Config) <-chan error {
+	timeout := conf.Timeout
 	ws := false
 	if to, ok := req.Context().Value(request.WebsocketsTimeout).(time.Duration); ok {
 		timeout = to
@@ -282,23 +353,65 @@ func (b *Backend) withTimeout(req *http.Request) <-chan error {
 	}
 
 	errCh := make(chan error, 1)
-	if timeout <= 0 {
+	if timeout+conf.TTFBTimeout <= 0 {
 		return errCh
 	}
 
-	ctx, cancel := context.WithCancel(req.Context())
-	*req = *req.WithContext(ctx)
+	ctx, cancel := context.WithCancel(context.WithValue(req.Context(), request.ConnectTimeout, conf.ConnectTimeout))
+
+	downstreamTrace := httptrace.ContextClientTrace(ctx) // e.g. log-timings
+
+	ttfbTimeout := make(chan time.Time, 1) // size to always cleanup related go-routine
+	ttfbTimer := time.NewTimer(conf.TTFBTimeout)
+	ctxTrace := &httptrace.ClientTrace{
+		WroteRequest: func(info httptrace.WroteRequestInfo) {
+			if downstreamTrace != nil && downstreamTrace.WroteRequest != nil {
+				downstreamTrace.WroteRequest(info)
+			}
+
+			if conf.TTFBTimeout <= 0 {
+				return
+			}
+
+			go func(done <-chan struct{}) {
+				ttfbTimer.Reset(conf.TTFBTimeout)
+				select {
+				case <-done:
+					if !ttfbTimer.Stop() {
+						<-ttfbTimer.C
+					}
+					return
+				case ttfbTimeout <- <-ttfbTimer.C:
+				}
+
+			}(req.Context().Done())
+		},
+		GotFirstResponseByte: func() {
+			if downstreamTrace != nil && downstreamTrace.GotFirstResponseByte != nil {
+				downstreamTrace.GotFirstResponseByte()
+			}
+			ttfbTimer.Stop()
+		},
+	}
+
+	*req = *req.WithContext(httptrace.WithClientTrace(ctx, ctxTrace))
+
 	go func(cancelFn func(), c context.Context, ec chan error) {
 		defer cancelFn()
-		deadline := time.After(timeout)
+		deadline := make(<-chan time.Time)
+		if timeout > 0 {
+			deadline = time.After(timeout)
+		}
 		select {
 		case <-deadline:
 			if ws {
 				ec <- errors.BackendTimeout.Label(b.name).Message("websockets: deadline exceeded")
-			} else {
-				ec <- errors.BackendTimeout.Label(b.name).Message("deadline exceeded")
+				return
 			}
+			ec <- errors.BackendTimeout.Label(b.name).Message("deadline exceeded")
 			return
+		case <-ttfbTimeout:
+			ec <- errors.BackendTimeout.Label(b.name).Message("timeout awaiting response headers")
 		case <-c.Done():
 			return
 		}
@@ -306,15 +419,16 @@ func (b *Backend) withTimeout(req *http.Request) <-chan error {
 	return errCh
 }
 
-func (b *Backend) evalTransport(httpCtx *hcl.EvalContext, req *http.Request) (*Config, error) {
+func (b *Backend) evalTransport(httpCtx *hcl.EvalContext, params hcl.Body, req *http.Request) (*Config, error) {
 	log := b.upstreamLog.LogEntry()
 
-	bodyContent, _, diags := b.context.PartialContent(config.BackendInlineSchema)
+	bodyContent, _, diags := params.PartialContent(config.BackendInlineSchema)
 	if diags.HasErrors() {
 		return nil, errors.Evaluation.Label(b.name).With(diags)
 	}
 
-	var origin, hostname, proxyURL string
+	var origin, hostname, proxyURL, oidcBackend string
+	var connectTimeout, ttfbTimeout, timeout string
 	type pair struct {
 		attrName string
 		target   *string
@@ -323,6 +437,11 @@ func (b *Backend) evalTransport(httpCtx *hcl.EvalContext, req *http.Request) (*C
 		{"origin", &origin},
 		{"hostname", &hostname},
 		{"proxy", &proxyURL},
+		{"_oidc_backend", &oidcBackend}, // prepared by config-load
+		// dynamic timings
+		{"connect_timeout", &connectTimeout},
+		{"ttfb_timeout", &ttfbTimeout},
+		{"timeout", &timeout},
 	} {
 		if v, err := eval.ValueFromAttribute(httpCtx, bodyContent, p.attrName); err != nil {
 			log.WithError(errors.Evaluation.Label(b.name).With(err)).Error()
@@ -339,20 +458,22 @@ func (b *Backend) evalTransport(httpCtx *hcl.EvalContext, req *http.Request) (*C
 			Messagef("invalid url: %s", originURL.String())
 	}
 
-	if rawURL, ok := req.Context().Value(request.URLAttribute).(string); ok {
-		urlAttr, err := url.Parse(rawURL)
+	urlAttrValue, _ := req.Context().Value(request.URLAttribute).(string)
+
+	if urlAttrValue != "" {
+		urlAttr, err := url.Parse(urlAttrValue)
 		if err != nil {
 			return nil, errors.Configuration.Label(b.name).With(err)
 		}
 
-		if origin != "" && urlAttr.Host != originURL.Host {
+		if origin != "" && oidcBackend == "" && urlAttr.Host != originURL.Host {
 			errctx := "url"
 			if tr := req.Context().Value(request.TokenRequest); tr != nil {
 				errctx = "token_endpoint"
 			}
 			return nil, errors.Configuration.Label(b.name).Kind(errctx).
-				Messagef("backend: the host '%s' must be equal to 'backend.origin': %q",
-					urlAttr.Host, origin)
+				Messagef("backend: the host '%s' must be equal to 'backend.origin' host: '%s'",
+					urlAttr.Host, originURL.Host)
 		}
 
 		originURL.Host = urlAttr.Host
@@ -377,7 +498,9 @@ func (b *Backend) evalTransport(httpCtx *hcl.EvalContext, req *http.Request) (*C
 			Messagef("the origin attribute has to contain an absolute URL with a valid hostname: %q", origin)
 	}
 
-	return b.transportConf.With(originURL.Scheme, originURL.Host, hostname, proxyURL), nil
+	return b.transportConf.
+		WithTarget(originURL.Scheme, originURL.Host, hostname, proxyURL).
+		WithTimings(connectTimeout, ttfbTimeout, timeout), nil
 }
 
 // setUserAgent sets an empty one if none is present or empty
diff --git a/handler/transport/backend_options.go b/handler/transport/backend_options.go
index babd2a9d..021d9ccd 100644
--- a/handler/transport/backend_options.go
+++ b/handler/transport/backend_options.go
@@ -1,8 +1,18 @@
 package transport
 
-import "github.com/avenga/couper/handler/validation"
+import (
+	"net/http"
+
+	"github.com/avenga/couper/handler/validation"
+)
 
 // BackendOptions represents the transport <BackendOptions> object.
 type BackendOptions struct {
-	OpenAPI *validation.OpenAPIOptions
+	OpenAPI     *validation.OpenAPIOptions
+	AuthBackend TokenRequest
+}
+
+type TokenRequest interface {
+	WithToken(req *http.Request) error
+	RetryWithToken(req *http.Request, res *http.Response) (bool, error)
 }
diff --git a/handler/transport/connection.go b/handler/transport/connection.go
index 6ad6301c..f79c8672 100644
--- a/handler/transport/connection.go
+++ b/handler/transport/connection.go
@@ -40,11 +40,6 @@ type OriginConn struct {
 
 // NewOriginConn creates a new wrapper with logging context.
 func NewOriginConn(ctx context.Context, conn net.Conn, conf *Config, entry *logrus.Entry) *OriginConn {
-	backendName := "default"
-	if conf.BackendName != "" {
-		backendName = conf.BackendName
-	}
-
 	var reqID string
 	if uid, ok := ctx.Value(request.UID).(string); ok {
 		reqID = uid
@@ -58,7 +53,7 @@ func NewOriginConn(ctx context.Context, conn net.Conn, conf *Config, entry *logr
 		labels: []attribute.KeyValue{
 			attribute.String("origin", conf.Origin),
 			attribute.String("host", conf.Hostname),
-			attribute.String("backend", backendName),
+			attribute.String("backend", conf.BackendName),
 		},
 		log:      entry,
 		tlsState: nil,
diff --git a/handler/transport/oauth2_req_auth.go b/handler/transport/oauth2_req_auth.go
index d7de56c8..0b36ed68 100644
--- a/handler/transport/oauth2_req_auth.go
+++ b/handler/transport/oauth2_req_auth.go
@@ -13,91 +13,88 @@ import (
 	"github.com/avenga/couper/oauth2"
 )
 
-var _ http.RoundTripper = &OAuth2ReqAuth{}
-
 // OAuth2ReqAuth represents the transport <OAuth2ReqAuth> object.
 type OAuth2ReqAuth struct {
-	oauth2Client *oauth2.CcClient
 	config       *config.OAuth2ReqAuth
-	memStore     *cache.MemoryStore
 	locks        sync.Map
-	next         http.RoundTripper
+	memStore     *cache.MemoryStore
+	oauth2Client *oauth2.ClientCredentialsClient
+	storageKey   string
 }
 
-// NewOAuth2ReqAuth creates a new <http.RoundTripper> object.
+// NewOAuth2ReqAuth implements the http.RoundTripper interface to wrap an existing Backend / http.RoundTripper
+// to retrieve a valid token before passing the initial out request.
 func NewOAuth2ReqAuth(conf *config.OAuth2ReqAuth, memStore *cache.MemoryStore,
-	oauth2Client *oauth2.CcClient, next http.RoundTripper) (http.RoundTripper, error) {
+	oauth2Client *oauth2.ClientCredentialsClient) (TokenRequest, error) {
 	return &OAuth2ReqAuth{
 		config:       conf,
 		oauth2Client: oauth2Client,
 		memStore:     memStore,
 		locks:        sync.Map{},
-		next:         next,
+		storageKey:   fmt.Sprintf("%p|%s|%s", &oauth2Client.Backend, conf.ClientID, conf.ClientSecret),
 	}, nil
 }
 
-// RoundTrip implements the <http.RoundTripper> interface.
-func (oa *OAuth2ReqAuth) RoundTrip(req *http.Request) (*http.Response, error) {
-	storageKey := fmt.Sprintf("%p|%s|%s", &oa.oauth2Client.Backend, oa.config.ClientID, oa.config.ClientSecret)
-
-	if token, terr := oa.readAccessToken(storageKey); terr != nil {
+func (oa *OAuth2ReqAuth) WithToken(req *http.Request) error {
+	if token, terr := oa.readAccessToken(); terr != nil {
 		// TODO this error is not connected to the OAuth2 client's backend
 		// In fact this can only be a JSON parse error or a missing access_token,
 		// which will occur after having requested the token from the authorization
 		// server. So the erroneous response will never be stored.
-		return nil, errors.Backend.Label(oa.config.BackendName).Message("token read error").With(terr)
+		return errors.Backend.Label(oa.config.BackendName).Message("token read error").With(terr)
 	} else if token != "" {
 		req.Header.Set("Authorization", "Bearer "+token)
-
-		return oa.next.RoundTrip(req)
+		return nil
 	}
 
-	value, _ := oa.locks.LoadOrStore(storageKey, &sync.Mutex{})
+	value, _ := oa.locks.LoadOrStore(oa.storageKey, &sync.Mutex{})
 	mutex := value.(*sync.Mutex)
 
 	mutex.Lock()
-	token, terr := oa.readAccessToken(storageKey)
+	token, terr := oa.readAccessToken()
 	if terr != nil {
 		mutex.Unlock()
-		return nil, errors.Backend.Label(oa.config.BackendName).Message("token read error").With(terr)
+		return errors.Backend.Label(oa.config.BackendName).Message("token read error").With(terr)
 	} else if token != "" {
 		mutex.Unlock()
 		req.Header.Set("Authorization", "Bearer "+token)
-		return oa.next.RoundTrip(req)
+		return nil
 	}
 
 	ctx := req.Context()
 	tokenResponse, tokenResponseData, token, err := oa.oauth2Client.GetTokenResponse(ctx)
 	if err != nil {
 		mutex.Unlock()
-		return nil, errors.Backend.Label(oa.config.BackendName).Message("token request error").With(err)
+		return errors.Backend.Label(oa.config.BackendName).Message("token request error").With(err)
 	}
 
-	oa.updateAccessToken(tokenResponse, tokenResponseData, storageKey)
+	oa.updateAccessToken(tokenResponse, tokenResponseData)
 	mutex.Unlock()
 
 	req.Header.Set("Authorization", "Bearer "+token)
+	return nil
+}
 
-	res, err := oa.next.RoundTrip(req)
-
-	if res != nil && res.StatusCode == http.StatusUnauthorized {
-		oa.memStore.Del(storageKey)
+func (oa *OAuth2ReqAuth) RetryWithToken(req *http.Request, res *http.Response) (bool, error) {
+	if res == nil || res.StatusCode != http.StatusUnauthorized {
+		return false, nil
+	}
 
-		if retries, ok := ctx.Value(request.TokenRequestRetries).(uint8); !ok || retries < *oa.config.Retries {
-			ctx = context.WithValue(ctx, request.TokenRequestRetries, retries+1)
+	oa.memStore.Del(oa.storageKey)
 
-			req.Header.Del("Authorization")
-			*req = *req.WithContext(ctx)
+	ctx := req.Context()
+	if retries, ok := ctx.Value(request.TokenRequestRetries).(uint8); !ok || retries < *oa.config.Retries {
+		ctx = context.WithValue(ctx, request.TokenRequestRetries, retries+1)
 
-			return oa.RoundTrip(req)
-		}
+		req.Header.Del("Authorization")
+		err := oa.WithToken(req.WithContext(ctx)) // WithContext due to header manipulation
+		return true, err
 	}
-
-	return res, err
+	return false, nil
 }
 
-func (oa *OAuth2ReqAuth) readAccessToken(key string) (string, error) {
-	if data := oa.memStore.Get(key); data != nil {
+func (oa *OAuth2ReqAuth) readAccessToken() (string, error) {
+	if data := oa.memStore.Get(oa.storageKey); data != nil {
 		_, token, err := oauth2.ParseTokenResponse(data.([]byte))
 		if err != nil {
 			// err can only be JSON parse error, however non-JSON data should never be stored
@@ -110,13 +107,13 @@ func (oa *OAuth2ReqAuth) readAccessToken(key string) (string, error) {
 	return "", nil
 }
 
-func (oa *OAuth2ReqAuth) updateAccessToken(jsonBytes []byte, jData map[string]interface{}, key string) {
+func (oa *OAuth2ReqAuth) updateAccessToken(jsonBytes []byte, jData map[string]interface{}) {
 	if oa.memStore != nil {
 		var ttl int64
 		if t, ok := jData["expires_in"].(float64); ok {
 			ttl = (int64)(t * 0.9)
 		}
 
-		oa.memStore.Set(key, jsonBytes, ttl)
+		oa.memStore.Set(oa.storageKey, jsonBytes, ttl)
 	}
 }
diff --git a/handler/transport/transport.go b/handler/transport/transport.go
index 0db233c8..87d0c5da 100644
--- a/handler/transport/transport.go
+++ b/handler/transport/transport.go
@@ -2,26 +2,23 @@ package transport
 
 import (
 	"context"
-	"crypto/sha256"
 	"crypto/tls"
 	"crypto/x509"
 	"fmt"
 	"net"
 	"net/http"
 	"net/url"
-	"sync"
 	"time"
 
 	"github.com/sirupsen/logrus"
 	"go.opentelemetry.io/otel/attribute"
 	"go.opentelemetry.io/otel/trace"
 
+	"github.com/avenga/couper/config/request"
 	"github.com/avenga/couper/telemetry"
 	"golang.org/x/net/http/httpproxy"
 )
 
-var transports sync.Map
-
 // Config represents the transport <Config> object.
 type Config struct {
 	BackendName            string
@@ -43,91 +40,86 @@ type Config struct {
 	Scheme   string
 }
 
-// Get creates a new <*http.Transport> object by the given <*Config>.
-func Get(conf *Config, log *logrus.Entry) *http.Transport {
-	key := conf.hash()
-
-	transport, ok := transports.Load(key)
-	if !ok {
-		certPool, err := x509.SystemCertPool()
-		if err == nil {
-			certPool.AppendCertsFromPEM(conf.Certificate)
-		}
+// NewTransport creates a new <*http.Transport> object by the given <*Config>.
+func NewTransport(conf *Config, log *logrus.Entry) *http.Transport {
+	certPool, err := x509.SystemCertPool()
+	if err == nil {
+		certPool.AppendCertsFromPEM(conf.Certificate)
+	}
 
-		tlsConf := &tls.Config{
-			InsecureSkipVerify: conf.DisableCertValidation,
-			RootCAs:            certPool,
-		}
-		if conf.Origin != conf.Hostname {
-			tlsConf.ServerName = conf.Hostname
-		}
+	tlsConf := &tls.Config{
+		InsecureSkipVerify: conf.DisableCertValidation,
+		RootCAs:            certPool,
+	}
 
-		d := &net.Dialer{
-			KeepAlive: 60 * time.Second,
-			Timeout:   conf.ConnectTimeout,
-		}
+	if conf.Origin != conf.Hostname {
+		tlsConf.ServerName = conf.Hostname
+	}
 
-		var proxyFunc func(req *http.Request) (*url.URL, error)
-		if conf.Proxy != "" {
-			proxyFunc = func(req *http.Request) (*url.URL, error) {
-				config := &httpproxy.Config{
-					HTTPProxy:  conf.Proxy,
-					HTTPSProxy: conf.Proxy,
-				}
+	d := &net.Dialer{
+		KeepAlive: 60 * time.Second,
+	}
 
-				return config.ProxyFunc()(req.URL)
+	var proxyFunc func(req *http.Request) (*url.URL, error)
+	if conf.Proxy != "" {
+		proxyFunc = func(req *http.Request) (*url.URL, error) {
+			config := &httpproxy.Config{
+				HTTPProxy:  conf.Proxy,
+				HTTPSProxy: conf.Proxy,
 			}
-		} else if !conf.NoProxyFromEnv {
-			proxyFunc = http.ProxyFromEnvironment
-		}
-
-		// This is the documented way to disable http2. However, if a custom tls.Config or
-		// DialContext is used h2 will also be disabled. To enable h2 the transport must be
-		// explicitly configured, this can be done with the 'ForceAttemptHTTP2' below.
-		var nextProto map[string]func(authority string, c *tls.Conn) http.RoundTripper
-		if !conf.HTTP2 {
-			nextProto = make(map[string]func(authority string, c *tls.Conn) http.RoundTripper)
-		}
 
-		logEntry := log.WithField("type", "couper_connection")
-
-		transport = &http.Transport{
-			DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
-				address := addr
-				if proxyFunc == nil {
-					address = conf.Origin
-				} // Otherwise, proxy connect will use this dial method and addr could be a proxy one.
-
-				stx, span := telemetry.NewSpanFromContext(ctx, "connect", trace.WithAttributes(attribute.String("couper.address", addr)))
-				defer span.End()
-
-				conn, err := d.DialContext(stx, network, address)
-				if err != nil {
-					return nil, fmt.Errorf("connecting to %s %q failed: %w", conf.BackendName, conf.Origin, err)
-				}
-				return NewOriginConn(stx, conn, conf, logEntry), nil
-			},
-			DisableCompression:    true,
-			DisableKeepAlives:     conf.DisableConnectionReuse,
-			ForceAttemptHTTP2:     conf.HTTP2,
-			MaxConnsPerHost:       conf.MaxConnections,
-			Proxy:                 proxyFunc,
-			ResponseHeaderTimeout: conf.TTFBTimeout,
-			TLSClientConfig:       tlsConf,
-			TLSNextProto:          nextProto,
+			return config.ProxyFunc()(req.URL)
 		}
+	} else if !conf.NoProxyFromEnv {
+		proxyFunc = http.ProxyFromEnvironment
+	}
 
-		transports.Store(key, transport)
+	// This is the documented way to disable http2. However, if a custom tls.Config or
+	// DialContext is used h2 will also be disabled. To enable h2 the transport must be
+	// explicitly configured, this can be done with the 'ForceAttemptHTTP2' below.
+	var nextProto map[string]func(authority string, c *tls.Conn) http.RoundTripper
+	if !conf.HTTP2 {
+		nextProto = make(map[string]func(authority string, c *tls.Conn) http.RoundTripper)
 	}
 
-	if t, ok := transport.(*http.Transport); ok {
-		return t
+	logEntry := log.WithField("type", "couper_connection")
+
+	transport := &http.Transport{
+		DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
+			address := addr
+			if proxyFunc == nil {
+				address = conf.Origin
+			} // Otherwise, proxy connect will use this dial method and addr could be a proxy one.
+
+			stx, span := telemetry.NewSpanFromContext(ctx, "connect", trace.WithAttributes(attribute.String("couper.address", addr)))
+			defer span.End()
+
+			connectTimeout, _ := ctx.Value(request.ConnectTimeout).(time.Duration)
+			if connectTimeout > 0 {
+				dtx, cancel := context.WithDeadline(stx, time.Now().Add(connectTimeout))
+				stx = dtx
+				defer cancel()
+			}
+
+			conn, cerr := d.DialContext(stx, network, address)
+			if cerr != nil {
+				return nil, fmt.Errorf("connecting to %s %q failed: %w", conf.BackendName, conf.Origin, cerr)
+			}
+			return NewOriginConn(stx, conn, conf, logEntry), nil
+		},
+		DisableCompression: true,
+		DisableKeepAlives:  conf.DisableConnectionReuse,
+		ForceAttemptHTTP2:  conf.HTTP2,
+		MaxConnsPerHost:    conf.MaxConnections,
+		Proxy:              proxyFunc,
+		TLSClientConfig:    tlsConf,
+		TLSNextProto:       nextProto,
 	}
 
-	return nil
+	return transport
 }
 
-func (c *Config) With(scheme, origin, hostname, proxyURL string) *Config {
+func (c *Config) WithTarget(scheme, origin, hostname, proxyURL string) *Config {
 	const defaultScheme = "http"
 	conf := *c
 	if scheme != "" {
@@ -160,8 +152,19 @@ func (c *Config) With(scheme, origin, hostname, proxyURL string) *Config {
 	return &conf
 }
 
-func (c *Config) hash() string {
-	h := sha256.New()
-	h.Write([]byte(fmt.Sprintf("%v", c)))
-	return fmt.Sprintf("%x", h.Sum(nil))
+func (c *Config) WithTimings(connect, ttfb, timeout string) *Config {
+	conf := *c
+	parseDuration(connect, &conf.ConnectTimeout)
+	parseDuration(ttfb, &conf.TTFBTimeout)
+	parseDuration(timeout, &conf.Timeout)
+	return &conf
+}
+
+// parseDuration sets the target value if the given duration string is not empty.
+func parseDuration(src string, target *time.Duration) {
+	d, err := time.ParseDuration(src)
+	if src != "" && err != nil {
+		return
+	}
+	*target = d
 }
diff --git a/handler/validation/openapi.go b/handler/validation/openapi.go
index 96ac4a01..3b77d84c 100644
--- a/handler/validation/openapi.go
+++ b/handler/validation/openapi.go
@@ -18,10 +18,11 @@ import (
 	"github.com/avenga/couper/eval"
 )
 
-var routersStore sync.Map
-
 type OpenAPI struct {
-	options *OpenAPIOptions
+	options    *OpenAPIOptions
+	router     routers.Router
+	syncRouter *sync.Once
+	syncErr    error
 }
 
 func NewOpenAPI(opts *OpenAPIOptions) *OpenAPI {
@@ -29,61 +30,52 @@ func NewOpenAPI(opts *OpenAPIOptions) *OpenAPI {
 		return nil
 	}
 	return &OpenAPI{
-		options: opts,
+		options:    opts,
+		syncRouter: &sync.Once{},
 	}
 }
 
-func (v *OpenAPI) getRouter(key, origin string) (routers.Router, error) {
-	router, exists := routersStore.Load(key)
-	if !exists {
-		clonedSwagger := cloneSwagger(v.options.doc)
-
-		var newServers []string
-		for _, s := range clonedSwagger.Servers {
-			// Do not touch a url if template variables are used.
-			if names, err := s.ParameterNames(); len(names) > 0 || err != nil {
-				continue
-			}
-
-			su, err := url.Parse(s.URL)
-			if err != nil {
-				return nil, err
-			}
+func (v *OpenAPI) newRouter(origin string) {
+	clonedSwagger := cloneSwagger(v.options.doc)
 
-			if !su.IsAbs() {
-				newServers = append(newServers, origin+s.URL)
-				continue
-			}
-
-			if su.Port() == "" && (su.Scheme == "https" || su.Scheme == "http") {
-				su.Host = su.Hostname() + ":"
-				if su.Scheme == "https" {
-					su.Host += "443"
-				} else {
-					su.Host += "80"
-				}
-				s.URL = su.String()
-			}
+	var newServers []string
+	for _, s := range clonedSwagger.Servers {
+		// Do not touch a url if template variables are used.
+		if names, err := s.ParameterNames(); len(names) > 0 || err != nil {
+			continue
+		}
 
+		su, err := url.Parse(s.URL)
+		if err != nil {
+			v.syncErr = err
+			return
 		}
 
-		for _, ns := range newServers {
-			clonedSwagger.AddServer(&openapi3.Server{URL: ns})
+		if !su.IsAbs() {
+			newServers = append(newServers, origin+s.URL)
+			continue
 		}
 
-		r, err := legacy.NewRouter(clonedSwagger)
-		if err != nil {
-			return nil, err
+		if su.Port() == "" && (su.Scheme == "https" || su.Scheme == "http") {
+			su.Host = su.Hostname() + ":"
+			if su.Scheme == "https" {
+				su.Host += "443"
+			} else {
+				su.Host += "80"
+			}
+			s.URL = su.String()
 		}
 
-		routersStore.Store(key, r)
-		return r, nil
 	}
 
-	return router.(routers.Router), nil
+	for _, ns := range newServers {
+		clonedSwagger.AddServer(&openapi3.Server{URL: ns})
+	}
+
+	v.router, v.syncErr = legacy.NewRouter(clonedSwagger)
 }
 
-func (v *OpenAPI) ValidateRequest(req *http.Request, key string) (*openapi3filter.RequestValidationInput, error) {
+func (v *OpenAPI) ValidateRequest(req *http.Request) (*openapi3filter.RequestValidationInput, error) {
 	// reqURL is modified due to origin transport configuration
 	serverURL := *req.URL
 	// possible hostname attribute override
@@ -92,7 +84,12 @@ func (v *OpenAPI) ValidateRequest(req *http.Request, key string) (*openapi3filte
 	} else {
 		serverURL.Host = req.Host + ":" + serverURL.Port()
 	}
-	router, err := v.getRouter(key, serverURL.Scheme+"://"+serverURL.Host)
+
+	v.syncRouter.Do(func() {
+		v.newRouter(serverURL.Scheme + "://" + serverURL.Host)
+	})
+
+	router, err := v.router, v.syncErr
 	if err != nil {
 		if ctx, ok := req.Context().Value(request.OpenAPI).(*OpenAPIContext); ok {
 			ctx.errors = append(ctx.errors, err)
diff --git a/json/synced_json.go b/json/synced_json.go
index bc6ca350..fd3afb72 100644
--- a/json/synced_json.go
+++ b/json/synced_json.go
@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"io/ioutil"
 	"net/http"
-	"sync"
 	"time"
 
 	"github.com/avenga/couper/config/reader"
@@ -13,108 +12,123 @@ import (
 )
 
 type SyncedJSONUnmarshaller interface {
-	Unmarshal(rawJSON []byte, uid string) (interface{}, error)
+	Unmarshal(rawJSON []byte) (interface{}, error)
+}
+
+type dataRequest struct {
+	obj interface{}
+	err error
 }
 
 type SyncedJSON struct {
-	context       context.Context
-	file          string
-	fileContext   string
-	uri           string
-	transport     http.RoundTripper
 	roundTripName string
+	transport     http.RoundTripper
 	ttl           time.Duration
 	unmarshaller  SyncedJSONUnmarshaller
+	uri           string
 	// used internally
-	data   interface{}
-	expiry int64
-	mtx    sync.RWMutex
+	data        interface{}
+	dataRequest chan chan *dataRequest
+	fileMode    bool
 }
 
-func NewSyncedJSON(context context.Context, file, fileContext, uri string, transport http.RoundTripper, roundTripName string, ttl time.Duration, unmarshaller SyncedJSONUnmarshaller) *SyncedJSON {
-	return &SyncedJSON{
-		context:       context,
-		file:          file,
-		fileContext:   fileContext,
-		uri:           uri,
-		transport:     transport,
+func NewSyncedJSON(file, fileContext, uri string, transport http.RoundTripper, roundTripName string, ttl time.Duration, unmarshaller SyncedJSONUnmarshaller) (*SyncedJSON, error) {
+	sj := &SyncedJSON{
+		dataRequest:   make(chan chan *dataRequest, 10),
 		roundTripName: roundTripName,
+		transport:     transport,
 		ttl:           ttl,
 		unmarshaller:  unmarshaller,
+		uri:           uri,
 	}
-}
 
-func (s *SyncedJSON) Data(uid string) (interface{}, error) {
-	var err error
-
-	s.mtx.RLock()
-	data := s.data
-	expired := s.hasExpired()
-	s.mtx.RUnlock()
-
-	if data == nil || expired {
-		data, err = s.Load(uid)
-		if err != nil {
-			return nil, fmt.Errorf("error loading synced JSON: %v", err)
+	if file != "" {
+		if err := sj.readFile(fileContext, file); err != nil {
+			return nil, err
 		}
+		sj.fileMode = true
+	} else if transport != nil {
+		go sj.sync(context.Background()) // TODO: at least cmd cancel ctx (reload)
+	} else {
+		return nil, fmt.Errorf("synced JSON: missing both file and request")
 	}
 
-	return data, nil
+	return sj, nil
 }
 
-func (s *SyncedJSON) Load(uid string) (interface{}, error) {
-	var rawJSON []byte
+func (s *SyncedJSON) sync(ctx context.Context) {
+	var expired <-chan time.Time
+	err := s.fetch() // initial fetch, provide any startup errors for first dataRequest's
+	if err != nil {
+		expired = time.After(0)
+	} else {
+		expired = time.After(s.ttl)
+	}
 
-	if s.file != "" {
-		j, err := reader.ReadFromFile(s.fileContext, s.file)
-		if err != nil {
-			return nil, err
-		}
-		rawJSON = j
-	} else if s.transport != nil {
-		req, err := http.NewRequest("GET", "", nil)
-		if err != nil {
-			return nil, err
-		}
-		ctx := context.WithValue(s.context, request.URLAttribute, s.uri)
-		ctx = context.WithValue(ctx, request.RoundTripName, s.roundTripName)
-		if uid != "" {
-			ctx = context.WithValue(ctx, request.UID, uid)
-		}
-		req = req.WithContext(ctx)
-		response, err := s.transport.RoundTrip(req)
-		if err != nil {
-			return nil, err
-		}
-		if response.StatusCode != 200 {
-			return nil, fmt.Errorf("status code %d", response.StatusCode)
+	for {
+		select {
+		case <-ctx.Done():
+			return
+		case <-expired:
+			err = s.fetch()
+			if err != nil {
+				time.Sleep(time.Second * 2)
+				continue
+			}
+			expired = time.After(s.ttl)
+		case r := <-s.dataRequest:
+			r <- &dataRequest{
+				err: err,
+				obj: s.data,
+			}
 		}
+	}
+}
 
-		defer response.Body.Close()
-
-		body, err := ioutil.ReadAll(response.Body)
-		if err != nil {
-			return nil, fmt.Errorf("error reading response for %q: %v", s.uri, err)
-		}
-		rawJSON = body
-	} else {
-		return nil, fmt.Errorf("synced JSON: missing both file and request")
+func (s *SyncedJSON) Data(_ string) (interface{}, error) {
+	if s.fileMode {
+		return s.data, nil
 	}
 
-	jsonData, err := s.unmarshaller.Unmarshal(rawJSON, uid)
+	rCh := make(chan *dataRequest)
+	s.dataRequest <- rCh
+	result := <-rCh
+	return result.obj, result.err
+}
+
+// fetch blocks all data reads until we will have an updated one.
+func (s *SyncedJSON) fetch() error {
+	req, _ := http.NewRequest("GET", s.uri, nil)
+
+	ctx := context.WithValue(context.Background(), request.URLAttribute, s.uri)
+	ctx = context.WithValue(ctx, request.RoundTripName, s.roundTripName)
+
+	req = req.WithContext(ctx)
+
+	response, err := s.transport.RoundTrip(req)
 	if err != nil {
-		return nil, err
+		return err
+	}
+	if response.StatusCode != http.StatusOK {
+		return fmt.Errorf("status code %d", response.StatusCode)
 	}
 
-	s.mtx.Lock()
-	defer s.mtx.Unlock()
+	defer response.Body.Close()
 
-	s.data = jsonData
-	s.expiry = time.Now().Unix() + int64(s.ttl.Seconds())
+	raw, err := ioutil.ReadAll(response.Body)
+	if err != nil {
+		return fmt.Errorf("error reading response for %q: %v", s.uri, err)
+	}
 
-	return jsonData, nil
+	s.data, err = s.unmarshaller.Unmarshal(raw)
+	return err
 }
 
-func (s *SyncedJSON) hasExpired() bool {
-	return time.Now().Unix() > s.expiry
+func (s *SyncedJSON) readFile(context, path string) error {
+	raw, err := reader.ReadFromFile(context, path)
+	if err != nil {
+		return err
+	}
+	s.data, err = s.unmarshaller.Unmarshal(raw)
+	return err
 }
diff --git a/logging/hooks/custom_logs.go b/logging/hooks/custom_logs.go
index 77efd733..b6f26dd2 100644
--- a/logging/hooks/custom_logs.go
+++ b/logging/hooks/custom_logs.go
@@ -42,9 +42,9 @@ func (c *CustomLogs) Fire(entry *logrus.Entry) error {
 		if t, exists := entry.Data["type"]; exists {
 			switch t {
 			case acTypeField:
-				fire(entry, request.LogCustomAccess)
+				fireAccess(entry)
 			case beTypeField:
-				fire(entry, request.LogCustomUpstream)
+				fireUpstream(entry)
 			}
 		}
 	}
@@ -52,13 +52,13 @@ func (c *CustomLogs) Fire(entry *logrus.Entry) error {
 	return nil
 }
 
-func fire(entry *logrus.Entry, bodyKey request.ContextKey) {
+func fireAccess(entry *logrus.Entry) {
 	evalCtx, ok := entry.Context.Value(request.ContextType).(*eval.Context)
 	if !ok {
 		return
 	}
 
-	bodies := entry.Context.Value(bodyKey)
+	bodies := entry.Context.Value(request.LogCustomAccess)
 	if bodies == nil {
 		return
 	}
@@ -68,29 +68,57 @@ func fire(entry *logrus.Entry, bodyKey request.ContextKey) {
 		return
 	}
 
-	ctx := evalCtx.HCLContextSync()
+	ctx := syncedUpstreamContext(evalCtx, entry)
+
+	if fields := eval.ApplyCustomLogs(ctx, hclBodies, entry); len(fields) > 0 {
+		entry.Data[customLogField] = fields
+	}
+}
+
+func fireUpstream(entry *logrus.Entry) {
+	evalCtx, ok := entry.Context.Value(request.ContextType).(*eval.Context)
+	if !ok {
+		return
+	}
+	bodyCh, _ := entry.Context.Value(request.LogCustomUpstream).(chan hcl.Body)
+	if bodyCh == nil {
+		return
+	}
 
-	if request.LogCustomUpstream == bodyKey {
-		if _, ok := ctx.Variables[eval.BackendRequests]; ok {
-			for k, v := range ctx.Variables[eval.BackendRequests].AsValueMap() {
-				if k == entry.Context.Value(request.RoundTripName) {
-					ctx.Variables[eval.BackendRequest] = v
-					break
-				}
+	var bodies []hcl.Body
+	select {
+	case body := <-bodyCh:
+		bodies = append(bodies, body)
+	case <-entry.Context.Done():
+	}
+
+	ctx := syncedUpstreamContext(evalCtx, entry)
+
+	if fields := eval.ApplyCustomLogs(ctx, bodies, entry); len(fields) > 0 {
+		entry.Data[customLogField] = fields
+	}
+}
+
+// syncedUpstreamContext prepares the local backend variable.
+func syncedUpstreamContext(evalCtx *eval.Context, entry *logrus.Entry) *hcl.EvalContext {
+	ctx := evalCtx.HCLContextSync()
+	if _, ok := ctx.Variables[eval.BackendRequests]; ok {
+		for k, v := range ctx.Variables[eval.BackendRequests].AsValueMap() {
+			if k == entry.Context.Value(request.RoundTripName) {
+				ctx.Variables[eval.BackendRequest] = v
+				break
 			}
 		}
+	}
 
-		if _, ok := ctx.Variables[eval.BackendResponses]; ok {
-			for k, v := range ctx.Variables[eval.BackendResponses].AsValueMap() {
-				if k == entry.Context.Value(request.RoundTripName) {
-					ctx.Variables[eval.BackendResponse] = v
-					break
-				}
+	if _, ok := ctx.Variables[eval.BackendResponses]; ok {
+		for k, v := range ctx.Variables[eval.BackendResponses].AsValueMap() {
+			if k == entry.Context.Value(request.RoundTripName) {
+				ctx.Variables[eval.BackendResponse] = v
+				break
 			}
 		}
 	}
 
-	if fields := eval.ApplyCustomLogs(ctx, hclBodies, entry); len(fields) > 0 {
-		entry.Data[customLogField] = fields
-	}
+	return ctx
 }
diff --git a/logging/upstream_log.go b/logging/upstream_log.go
index e12d51d2..a2bd7f11 100644
--- a/logging/upstream_log.go
+++ b/logging/upstream_log.go
@@ -1,6 +1,7 @@
 package logging
 
 import (
+	"context"
 	"crypto/tls"
 	"net/http"
 	"net/http/httptrace"
@@ -14,6 +15,7 @@ import (
 	"github.com/avenga/couper/config/request"
 	"github.com/avenga/couper/errors"
 	"github.com/avenga/couper/handler/validation"
+	"github.com/hashicorp/hcl/v2"
 )
 
 var _ http.RoundTripper = &UpstreamLog{}
@@ -72,13 +74,17 @@ func (u *UpstreamLog) RoundTrip(req *http.Request) (*http.Response, error) {
 
 	fields["request"] = requestFields
 
-	oCtx, openAPIContext := validation.NewWithContext(req.Context())
+	logCtxCh := make(chan hcl.Body, 10)
+	outctx := context.WithValue(req.Context(), request.LogCustomUpstream, logCtxCh)
+	oCtx, openAPIContext := validation.NewWithContext(outctx)
 	outreq := req.WithContext(httptrace.WithClientTrace(oCtx, clientTrace))
 
 	rtStart := time.Now()
 	beresp, err := u.next.RoundTrip(outreq)
 	rtDone := time.Now()
 
+	close(logCtxCh)
+
 	if outreq.Host != "" {
 		requestFields["origin"] = outreq.Host
 		requestFields["host"], requestFields["port"] = splitHostPort(outreq.Host)
diff --git a/oauth2/ac.go b/oauth2/abstract_auth_client.go
similarity index 58%
rename from oauth2/ac.go
rename to oauth2/abstract_auth_client.go
index 86d659df..49d61eb8 100644
--- a/oauth2/ac.go
+++ b/oauth2/abstract_auth_client.go
@@ -17,25 +17,18 @@ import (
 	"github.com/avenga/couper/internal/seetie"
 )
 
-// AcClient represents an OAuth2 client using the authorization code flow.
-type AcClient interface {
-	GetName() string
-	GetTokenResponse(req *http.Request, callbackURL *url.URL) (map[string]interface{}, error)
-	validateTokenResponseData(ctx context.Context, tokenResponseData map[string]interface{}, hashedVerifierValue, verifierValue, accessToken string) error
-}
-
-type AbstractAcClient struct {
-	AcClient
-	Client
+type AbstractAuthCodeClient struct {
+	AuthCodeFlowClient
+	*Client
 	name string
 }
 
-func (a AbstractAcClient) GetName() string {
+func (a AbstractAuthCodeClient) GetName() string {
 	return a.name
 }
 
-// GetTokenResponse retrieves the response from the token endpoint
-func (a AbstractAcClient) GetTokenResponse(req *http.Request, callbackURL *url.URL) (map[string]interface{}, error) {
+// GetTokenResponse retrieves the response from the token endpoint.
+func (a AbstractAuthCodeClient) GetTokenResponse(req *http.Request, callbackURL *url.URL) (map[string]interface{}, error) {
 	query := callbackURL.Query()
 	code := query.Get("code")
 	if code == "" {
@@ -109,54 +102,6 @@ func (a AbstractAcClient) GetTokenResponse(req *http.Request, callbackURL *url.U
 	return tokenResponseData, nil
 }
 
-// OAuth2AcClient represents an OAuth2 client using the (plain) authorization code flow.
-type OAuth2AcClient struct {
-	*AbstractAcClient
-}
-
-// NewOAuth2AC creates a new OAuth2 Authorization Code client.
-func NewOAuth2AC(acClientConf config.OAuth2AcClient, oauth2AsConf config.OAuth2AS, backend http.RoundTripper) (*OAuth2AcClient, error) {
-	grantType := acClientConf.GetGrantType()
-	if grantType != "authorization_code" {
-		return nil, fmt.Errorf("grant_type %s not supported", grantType)
-	}
-
-	if teAuthMethod := acClientConf.GetTokenEndpointAuthMethod(); teAuthMethod != nil {
-		if *teAuthMethod != "client_secret_basic" && *teAuthMethod != "client_secret_post" {
-			return nil, fmt.Errorf("token_endpoint_auth_method %s not supported", *teAuthMethod)
-		}
-	}
-
-	switch acClientConf.(type) {
-	case *config.OAuth2AC:
-		verifierMethod, err := acClientConf.GetVerifierMethod("")
-		if err != nil {
-			return nil, err
-		}
-
-		if verifierMethod != config.CcmS256 && verifierMethod != "nonce" && verifierMethod != "state" {
-			return nil, fmt.Errorf("verifier_method %s not supported", verifierMethod)
-		}
-	default:
-		// skip this for oidc configurations due to possible startup errors
-	}
-
-	client := Client{
-		Backend:      backend,
-		asConfig:     oauth2AsConf,
-		clientConfig: acClientConf,
-		grantType:    grantType,
-	}
-	o := &OAuth2AcClient{&AbstractAcClient{Client: client, name: acClientConf.GetName()}}
-	o.AcClient = o
-	return o, nil
-}
-
-// validateTokenResponseData validates the token response data (no-op)
-func (o *OAuth2AcClient) validateTokenResponseData(_ context.Context, _ map[string]interface{}, _, _, _ string) error {
-	return nil
-}
-
 func Base64urlSha256(value string) string {
 	h := sha256.New()
 	h.Write([]byte(value))
diff --git a/oauth2/auth_code_client.go b/oauth2/auth_code_client.go
new file mode 100644
index 00000000..a1d06931
--- /dev/null
+++ b/oauth2/auth_code_client.go
@@ -0,0 +1,68 @@
+package oauth2
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"net/url"
+
+	"github.com/avenga/couper/config"
+)
+
+// AuthCodeFlowClient represents an OAuth2 client using the authorization code flow.
+type AuthCodeFlowClient interface {
+	GetName() string
+	GetTokenResponse(req *http.Request, callbackURL *url.URL) (map[string]interface{}, error)
+	validateTokenResponseData(ctx context.Context, tokenResponseData map[string]interface{}, hashedVerifierValue, verifierValue, accessToken string) error
+}
+
+// AuthCodeClient represents an OAuth2 client using the (plain) authorization code flow.
+type AuthCodeClient struct {
+	*AbstractAuthCodeClient
+}
+
+// NewAuthCodeClient creates a new OAuth2 Authorization Code client.
+func NewAuthCodeClient(acClientConf config.OAuth2AcClient, oauth2AsConf config.OAuth2AS, backend http.RoundTripper) (*AuthCodeClient, error) {
+	grantType := acClientConf.GetGrantType()
+	if grantType != "authorization_code" {
+		return nil, fmt.Errorf("grant_type %s not supported", grantType)
+	}
+
+	if teAuthMethod := acClientConf.GetTokenEndpointAuthMethod(); teAuthMethod != nil {
+		if *teAuthMethod != "client_secret_basic" && *teAuthMethod != "client_secret_post" {
+			return nil, fmt.Errorf("token_endpoint_auth_method %s not supported", *teAuthMethod)
+		}
+	}
+
+	switch acClientConf.(type) {
+	case *config.OAuth2AC:
+		verifierMethod, err := acClientConf.GetVerifierMethod("")
+		if err != nil {
+			return nil, err
+		}
+
+		if verifierMethod != config.CcmS256 && verifierMethod != "nonce" && verifierMethod != "state" {
+			return nil, fmt.Errorf("verifier_method %s not supported", verifierMethod)
+		}
+	default:
+		// skip this for oidc configurations due to possible startup errors
+	}
+
+	client := &Client{
+		Backend:      backend,
+		asConfig:     oauth2AsConf,
+		clientConfig: acClientConf,
+		grantType:    grantType,
+	}
+	o := &AuthCodeClient{&AbstractAuthCodeClient{
+		Client: client,
+		name:   acClientConf.GetName(),
+	}}
+	o.AuthCodeFlowClient = o
+	return o, nil
+}
+
+// validateTokenResponseData validates the token response data (no-op)
+func (o *AuthCodeClient) validateTokenResponseData(_ context.Context, _ map[string]interface{}, _, _, _ string) error {
+	return nil
+}
diff --git a/oauth2/client.go b/oauth2/client.go
index afda9587..bfde6500 100644
--- a/oauth2/client.go
+++ b/oauth2/client.go
@@ -33,10 +33,11 @@ func (c *Client) requestToken(ctx context.Context, requestParams map[string]stri
 	if err != nil {
 		return nil, 0, err
 	}
+	defer tokenRes.Body.Close()
 
 	tokenResBytes, err := io.ReadAll(tokenRes.Body)
 	if err != nil {
-		return nil, 0, err
+		return nil, tokenRes.StatusCode, err
 	}
 
 	return tokenResBytes, tokenRes.StatusCode, nil
diff --git a/oauth2/cc.go b/oauth2/client_cred_client.go
similarity index 79%
rename from oauth2/cc.go
rename to oauth2/client_cred_client.go
index be35ed97..b8bd9aca 100644
--- a/oauth2/cc.go
+++ b/oauth2/client_cred_client.go
@@ -8,16 +8,16 @@ import (
 	"github.com/avenga/couper/errors"
 )
 
-// CcClient represents an OAuth2 client using the client credentials flow.
-type CcClient struct {
+// ClientCredentialsClient represents an OAuth2 client using the client credentials flow.
+type ClientCredentialsClient struct {
 	*Client
 	// password and username undocumented feature!
 	password string
 	username string
 }
 
-// NewOAuth2CC creates a new OAuth2 Client Credentials client.
-func NewOAuth2CC(conf *config.OAuth2ReqAuth, backend http.RoundTripper) (*CcClient, error) {
+// NewClientCredentialsClient creates a new OAuth2 Client Credentials client.
+func NewClientCredentialsClient(conf *config.OAuth2ReqAuth, backend http.RoundTripper) (*ClientCredentialsClient, error) {
 	backendErr := errors.Backend.Label(conf.Reference())
 	// grant_type password undocumented feature!
 	if conf.GrantType != "client_credentials" && conf.GrantType != "password" {
@@ -53,11 +53,11 @@ func NewOAuth2CC(conf *config.OAuth2ReqAuth, backend http.RoundTripper) (*CcClie
 		}
 	}
 	// conf.Password and conf.Username undocumented feature!
-	return &CcClient{&Client{backend, conf, conf, conf.GrantType}, conf.Password, conf.Username}, nil
+	return &ClientCredentialsClient{&Client{backend, conf, conf, conf.GrantType}, conf.Password, conf.Username}, nil
 }
 
 // GetTokenResponse retrieves the response from the token endpoint
-func (c *CcClient) GetTokenResponse(ctx context.Context) ([]byte, map[string]interface{}, string, error) {
+func (c *ClientCredentialsClient) GetTokenResponse(ctx context.Context) ([]byte, map[string]interface{}, string, error) {
 	var requestParams map[string]string
 	// password and username undocumented feature!
 	if c.password != "" || c.username != "" {
diff --git a/oauth2/oidc/config.go b/oauth2/oidc/config.go
index 5850819f..0106e9bf 100644
--- a/oauth2/oidc/config.go
+++ b/oauth2/oidc/config.go
@@ -9,7 +9,9 @@ import (
 	"time"
 
 	"github.com/avenga/couper/accesscontrol/jwk"
+	"github.com/avenga/couper/backend"
 	"github.com/avenga/couper/config"
+	hclbody "github.com/avenga/couper/config/body"
 	jsn "github.com/avenga/couper/json"
 )
 
@@ -36,15 +38,16 @@ var (
 // Config represents the configuration for an OIDC client
 type Config struct {
 	*config.OIDC
-	Backend    http.RoundTripper
+	backends   map[string]http.RoundTripper
 	context    context.Context
 	syncedJSON *jsn.SyncedJSON
-	JWKS       *jwk.JWKS
-	mtx        sync.RWMutex
+	jwks       *jwk.JWKS
+	cmu        sync.RWMutex // conf
+	jmu        sync.RWMutex // jkws
 }
 
 // NewConfig creates a new configuration for an OIDC client
-func NewConfig(oidc *config.OIDC, backend http.RoundTripper) (*Config, error) {
+func NewConfig(oidc *config.OIDC, backends map[string]http.RoundTripper) (*Config, error) {
 	ttl := defaultTTL
 	if oidc.ConfigurationTTL != "" {
 		t, err := time.ParseDuration(oidc.ConfigurationTTL)
@@ -55,20 +58,47 @@ func NewConfig(oidc *config.OIDC, backend http.RoundTripper) (*Config, error) {
 	}
 
 	ctx := context.Background()
-	config := &Config{OIDC: oidc, context: ctx, Backend: backend}
-	sj := jsn.NewSyncedJSON(ctx, "", "", oidc.ConfigurationURL, backend, oidc.Name, ttl, config)
-	config.syncedJSON = sj
-	return config, nil
+	conf := &Config{
+		OIDC:     oidc,
+		backends: backends,
+		context:  ctx,
+	}
+
+	var err error
+	conf.syncedJSON, err = jsn.NewSyncedJSON("", "",
+		oidc.ConfigurationURL, backends["configuration_backend"], oidc.Name, ttl, conf)
+	if err != nil {
+		return nil, err
+	}
+
+	// verify verifierMethod with locked access
+	conf.jmu.RLock()
+	defer conf.jmu.RUnlock()
+	if conf.VerifierMethod != "" &&
+		conf.VerifierMethod != config.CcmS256 &&
+		conf.VerifierMethod != "nonce" {
+		return nil, fmt.Errorf("verifier_method %s not supported", conf.VerifierMethod)
+	}
+
+	return conf, err
+}
+
+func (c *Config) Backends() map[string]http.RoundTripper {
+	return c.backends
 }
 
 // GetVerifierMethod retrieves the verifier method (ccm_s256 or nonce)
 func (c *Config) GetVerifierMethod(uid string) (string, error) {
+	c.jmu.RLock()
 	if c.VerifierMethod == "" {
+		c.jmu.RUnlock()
 		_, err := c.Data(uid)
 		if err != nil {
 			return "", err
 		}
+		c.jmu.RLock()
 	}
+	defer c.jmu.RUnlock()
 
 	return c.VerifierMethod, nil
 }
@@ -115,42 +145,49 @@ func (c *Config) Data(uid string) (*OpenidConfiguration, error) {
 		return nil, err
 	}
 
-	openidConfigurationData, ok := data.(OpenidConfiguration)
+	openidConfigurationData, ok := data.(*OpenidConfiguration)
 	if !ok {
 		return nil, fmt.Errorf("data not OpenID configuration data: %#v", data)
 	}
 
-	return &openidConfigurationData, nil
+	return openidConfigurationData, nil
+}
+
+func (c *Config) JWKS() *jwk.JWKS {
+	c.jmu.RLock()
+	defer c.jmu.RUnlock()
+	return c.jwks
 }
 
-func (c *Config) Unmarshal(rawJSON []byte, uid string) (interface{}, error) {
-	var jsonData OpenidConfiguration
-	err := json.Unmarshal(rawJSON, &jsonData)
+func (c *Config) Unmarshal(rawJSON []byte) (interface{}, error) {
+	c.jmu.Lock()
+	defer c.jmu.Unlock()
+
+	jsonData := &OpenidConfiguration{}
+	err := json.Unmarshal(rawJSON, jsonData)
 	if err != nil {
 		return nil, err
 	}
 
-	c.mtx.RLock()
-	oldVM := c.OIDC.VerifierMethod
-	c.mtx.RUnlock()
-	newVM := oldVM
-	if oldVM == "" {
+	if c.OIDC.VerifierMethod == "" {
 		if supportsS256(jsonData.CodeChallengeMethodsSupported) {
-			newVM = config.CcmS256
+			c.OIDC.VerifierMethod = config.CcmS256
 		} else {
-			newVM = "nonce"
+			c.OIDC.VerifierMethod = "nonce"
 		}
 	}
 
-	newJWKS, err := jwk.NewJWKS(jsonData.JwksUri, c.OIDC.ConfigurationTTL, c.Backend, c.context)
-	if err != nil {
-		return nil, err
+	jwksBackend := backend.NewContext(hclbody.
+		New(hclbody.NewContentWithAttrName("_backend_url", jsonData.JwksUri)),
+		c.backends["jwks_uri_backend"],
+	)
+
+	newJWKS, err := jwk.NewJWKS(jsonData.JwksUri, c.OIDC.ConfigurationTTL, jwksBackend)
+	if err != nil { // do not replace possible working jwks on err
+		return jsonData, err
 	}
-	newJWKS.Data(uid)
-	c.mtx.Lock()
-	c.OIDC.VerifierMethod = newVM
-	c.JWKS = newJWKS
-	c.mtx.Unlock()
+
+	c.jwks = newJWKS
 
 	return jsonData, nil
 }
diff --git a/oauth2/oidc.go b/oauth2/oidc_client.go
similarity index 92%
rename from oauth2/oidc.go
rename to oauth2/oidc_client.go
index 7c676e52..deb8f191 100644
--- a/oauth2/oidc.go
+++ b/oauth2/oidc_client.go
@@ -16,27 +16,30 @@ import (
 	"github.com/avenga/couper/oauth2/oidc"
 )
 
-// OidcClient represents an OIDC client using the authorization code flow.
+// OidcClient represents an OpenID Connect client using the authorization code flow.
 type OidcClient struct {
-	*OAuth2AcClient
+	*AuthCodeClient
 	config    *oidc.Config
+	backends  map[string]http.RoundTripper
 	issLock   sync.RWMutex
 	issuer    string
 	jwtParser *jwt.Parser
 }
 
-// NewOidc creates a new OIDC client.
-func NewOidc(oidcConfig *oidc.Config) (*OidcClient, error) {
-	acClient, err := NewOAuth2AC(oidcConfig, oidcConfig, oidcConfig.Backend)
+// NewOidcClient creates a new OIDC client.
+func NewOidcClient(oidcConfig *oidc.Config) (*OidcClient, error) {
+	o := &OidcClient{
+		config:   oidcConfig,
+		backends: oidcConfig.Backends(),
+	}
+
+	acClient, err := NewAuthCodeClient(oidcConfig, oidcConfig, o.backends["token_backend"])
 	if err != nil {
 		return nil, err
 	}
 
-	o := &OidcClient{
-		OAuth2AcClient: acClient,
-		config:         oidcConfig,
-	}
-	o.AcClient = o
+	o.AuthCodeClient = acClient
+	o.AuthCodeFlowClient = o
 	return o, nil
 }
 
@@ -95,7 +98,8 @@ func (o *OidcClient) validateTokenResponseData(ctx context.Context, tokenRespons
 	o.issLock.RUnlock()
 
 	if idTokenString, ok := tokenResponseData["id_token"].(string); ok {
-		idToken, err := jwtParser.Parse(idTokenString, o.config.JWKS.GetSigKeyForToken)
+
+		idToken, err := jwtParser.Parse(idTokenString, o.Keyfunc)
 		if err != nil {
 			return err
 		}
@@ -131,6 +135,11 @@ func (o *OidcClient) validateTokenResponseData(ctx context.Context, tokenRespons
 	return errors.Oauth2.Message("missing id_token in token response")
 }
 
+func (o *OidcClient) Keyfunc(token *jwt.Token) (interface{}, error) {
+	return o.config.JWKS().
+		GetSigKeyForToken(token)
+}
+
 func (o *OidcClient) validateIdTokenClaims(ctx context.Context, claims jwt.Claims, hashedVerifierValue, verifierValue string, accessToken string) (map[string]interface{}, map[string]interface{}, error) {
 	var idTokenClaims jwt.MapClaims
 	if tc, ok := claims.(jwt.MapClaims); ok {
@@ -241,7 +250,7 @@ func (o *OidcClient) requestUserinfo(ctx context.Context, accessToken string) ([
 		return nil, err
 	}
 
-	userinfoRes, err := o.Backend.RoundTrip(userinfoReq)
+	userinfoRes, err := o.backends["userinfo_backend"].RoundTrip(userinfoReq)
 	if err != nil {
 		return nil, err
 	}
diff --git a/server/writer/gzip.go b/server/writer/gzip.go
index 1d595738..b6494ce8 100644
--- a/server/writer/gzip.go
+++ b/server/writer/gzip.go
@@ -140,7 +140,7 @@ func (g *Gzip) writeHeader() {
 		g.rw.Header().Set(ContentEncodingHeader, GzipName)
 	}
 
-	if !g.hijacked {
+	if !g.hijacked && g.statusCode > 0 {
 		g.rw.WriteHeader(g.statusCode)
 	}
 }


Test patch:
diff --git a/accesscontrol/jwk/jwks_test.go b/accesscontrol/jwk/jwks_test.go
index c3e379c5..67d6f2ed 100644
--- a/accesscontrol/jwk/jwks_test.go
+++ b/accesscontrol/jwk/jwks_test.go
@@ -1,16 +1,33 @@
 package jwk_test
 
 import (
+	"bytes"
+	"context"
+	"io"
+	"net/http"
+	"net/http/httptest"
+	"os"
 	"sync"
 	"testing"
 
 	"github.com/dgrijalva/jwt-go/v4"
 
 	"github.com/avenga/couper/accesscontrol/jwk"
+	"github.com/avenga/couper/config/body"
+	"github.com/avenga/couper/handler/transport"
 	"github.com/avenga/couper/internal/test"
 )
 
 func Test_JWKS(t *testing.T) {
+
+	origin := test.NewBackend()
+	defer origin.Close()
+
+	log, _ := test.NewLogger()
+
+	backend := transport.NewBackend(body.New(body.NewContentWithAttrName("origin", origin.Addr())),
+		&transport.Config{}, nil, log.WithContext(context.Background()))
+
 	tests := []struct {
 		name  string
 		url   string
@@ -19,18 +36,17 @@ func Test_JWKS(t *testing.T) {
 		{"missing_scheme", "no-scheme", `unsupported JWKS URI scheme: "no-scheme"`},
 		{"short url", "file", `unsupported JWKS URI scheme: "file"`},
 		{"short url", "https", `unsupported JWKS URI scheme: "https"`},
-		{"ok file", "file:jwks.json", ""},
-		{"ok http", "http://jwks.json", ""},
-		{"ok https", "https://jwks.json", ""},
+		{"ok file", "file:testdata/jwks.json", ""},
+		{"ok http", origin.Addr() + "/jwks.json", ""},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(subT *testing.T) {
-			_, err := jwk.NewJWKS(tt.url, "", nil, nil)
+			_, err := jwk.NewJWKS(tt.url, "", backend)
 			if err == nil && tt.error != "" {
 				subT.Errorf("Missing error:\n\tWant: %v\n\tGot:  %v", tt.error, nil)
 			}
 			if err != nil && err.Error() != tt.error {
-				subT.Errorf("Unexpected error:\n\tWant: %v\n\tGot:  %v", tt.error, err)
+				subT.Errorf("Unexpected error:\n\tWant: %v\n\tGot:  %#v", tt.error, err)
 			}
 		})
 	}
@@ -48,7 +64,7 @@ func Test_JWKS_Load(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(subT *testing.T) {
-			jwks, err := jwk.NewJWKS("file:"+tt.file, "", nil, nil)
+			jwks, err := jwk.NewJWKS("file:"+tt.file, "", nil)
 			helper.Must(err)
 			_, err = jwks.Data("")
 			if err != nil && tt.expParsed {
@@ -77,7 +93,7 @@ func Test_JWKS_GetSigKeyForToken(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(subT *testing.T) {
 			helper := test.New(subT)
-			jwks, err := jwk.NewJWKS("file:"+tt.file, "", nil, nil)
+			jwks, err := jwk.NewJWKS("file:"+tt.file, "", nil)
 			helper.Must(err)
 			_, err = jwks.Data("")
 			helper.Must(err)
@@ -129,7 +145,7 @@ func Test_JWKS_GetKey(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(subT *testing.T) {
 			helper := test.New(subT)
-			jwks, err := jwk.NewJWKS("file:"+tt.file, "", nil, nil)
+			jwks, err := jwk.NewJWKS("file:"+tt.file, "", nil)
 			helper.Must(err)
 			_, err = jwks.Data("")
 			helper.Must(err)
@@ -150,7 +166,16 @@ func Test_JWKS_LoadSynced(t *testing.T) {
 	memQuitCh := make(chan struct{})
 	defer close(memQuitCh)
 
-	jwks, err := jwk.NewJWKS("file:testdata/jwks.json", "", nil, nil)
+	jwksOrigin := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
+		f, err := os.ReadFile("testdata/jwks.json")
+		if err != nil {
+			writer.WriteHeader(http.StatusInternalServerError)
+			return
+		}
+		io.Copy(writer, bytes.NewReader(f))
+	}))
+
+	jwks, err := jwk.NewJWKS(jwksOrigin.URL, "10s", http.DefaultTransport)
 	helper.Must(err)
 
 	wg := sync.WaitGroup{}
@@ -158,8 +183,7 @@ func Test_JWKS_LoadSynced(t *testing.T) {
 	for i := 0; i < 10; i++ {
 		go func(idx int) {
 			defer wg.Done()
-
-			_, e := jwks.GetKeys("kid1")
+			_, e := jwks.GetKey("kid1", "", "")
 			helper.Must(e)
 		}(i)
 	}
diff --git a/accesscontrol/jwt_test.go b/accesscontrol/jwt_test.go
index d40390c8..672c0608 100644
--- a/accesscontrol/jwt_test.go
+++ b/accesscontrol/jwt_test.go
@@ -22,6 +22,7 @@ import (
 
 	ac "github.com/avenga/couper/accesscontrol"
 	acjwt "github.com/avenga/couper/accesscontrol/jwt"
+	"github.com/avenga/couper/cache"
 	"github.com/avenga/couper/config/configload"
 	"github.com/avenga/couper/config/reader"
 	"github.com/avenga/couper/config/request"
@@ -675,7 +676,7 @@ func TestJwtConfig(t *testing.T) {
 			  }
 			}
 			`,
-			"signature_algorithm or jwks_url required",
+			"signature_algorithm or jwks_url attribute required",
 		},
 		{
 			"signature_algorithm, missing key/key_file",
@@ -810,12 +811,12 @@ func TestJwtConfig(t *testing.T) {
 			server "test" {}
 			definitions {
 			  jwt "myac" {
-			    jwks_url = "http://..."
+			    jwks_url = "http://no-back.end"
 			    header = "..."
 			  }
 			}
 			`,
-			"",
+			`backend error: anonymous_5_8_jwks_url: connecting to anonymous_5_8_jwks_url "no-back.end:80" failed: dial tcp: lookup no-back.end`,
 		},
 		{
 			"signature_algorithm + jwks_url",
@@ -824,7 +825,7 @@ func TestJwtConfig(t *testing.T) {
 			definitions {
 			  jwt "myac" {
 			    signature_algorithm = "HS256"
-			    jwks_url = "http://..."
+			    jwks_url = "http://no-back.end"
 			    header = "..."
 			  }
 			}
@@ -838,7 +839,7 @@ func TestJwtConfig(t *testing.T) {
 			definitions {
 			  jwt "myac" {
 			    key = "..."
-			    jwks_url = "http://..."
+			    jwks_url = "http://no-back.end"
 			    header = "..."
 			  }
 			}
@@ -852,7 +853,7 @@ func TestJwtConfig(t *testing.T) {
 			definitions {
 			  jwt "myac" {
 			    key_file = "..."
-			    jwks_url = "http://..."
+			    jwks_url = "http://no-back.end"
 			    header = "..."
 			  }
 			}
@@ -867,11 +868,12 @@ func TestJwtConfig(t *testing.T) {
 			  jwt "myac" {
 			    backend = "foo"
 			    header = "..."
+				signature_algorithm = "asdf"
 			  }
 			  backend "foo" {}
 			}
 			`,
-			"backend not needed without jwks_url",
+			"backend is obsolete without jwks_url attribute",
 		},
 		{
 			"ok: jwks_url + backend reference",
@@ -881,12 +883,12 @@ func TestJwtConfig(t *testing.T) {
 			  jwt "myac" {
 			    backend = "foo"
 			    header = "..."
-			    jwks_url = "http://..."
+			    jwks_url = "http://no-back.end"
 			  }
 			  backend "foo" {}
 			}
 			`,
-			"",
+			`backend error: foo: connecting to foo "no-back.end:80" failed: dial tcp: lookup no-back.end`,
 		},
 	}
 
@@ -896,26 +898,31 @@ func TestJwtConfig(t *testing.T) {
 		t.Run(tt.name, func(subT *testing.T) {
 			conf, err := configload.LoadBytes([]byte(tt.hcl), "couper.hcl")
 			if conf != nil {
-				_, err = runtime.NewServerConfiguration(conf, log.WithContext(context.TODO()), nil)
+				logger := log.WithContext(context.TODO())
+
+				tmpStoreCh := make(chan struct{})
+				defer close(tmpStoreCh)
+
+				_, err = runtime.NewServerConfiguration(conf, logger, cache.New(logger, tmpStoreCh))
 			}
 
-			var error = ""
+			var errMsg = ""
 			if err != nil {
 				if _, ok := err.(errors.GoError); ok {
-					error = err.(errors.GoError).LogError()
+					errMsg = err.(errors.GoError).LogError()
 				} else {
-					error = err.Error()
+					errMsg = err.Error()
 				}
 			}
 
-			if tt.error == "" && error == "" {
+			if tt.error == "" && errMsg == "" {
 				return
 			}
 
 			expectedError := "configuration error: myac: " + tt.error
 
-			if expectedError != error {
-				subT.Errorf("%q: Unexpected configuration error:\n\tWant: %q\n\tGot:  %q", tt.name, expectedError, error)
+			if !strings.HasPrefix(errMsg, expectedError) {
+				subT.Errorf("%q: Unexpected configuration error:\n\tWant: %q\n\tGot:  %q", tt.name, expectedError, errMsg)
 			}
 		})
 	}
diff --git a/config/configload/load_test.go b/config/configload/load_test.go
new file mode 100644
index 00000000..a4c64e69
--- /dev/null
+++ b/config/configload/load_test.go
@@ -0,0 +1,72 @@
+package configload_test
+
+import (
+	"fmt"
+	"strings"
+	"testing"
+
+	"github.com/avenga/couper/config/configload"
+)
+
+func TestPrepareBackendRefineAttributes(t *testing.T) {
+	config := `server {
+	endpoint "/" {
+		request {
+			backend "ref" {
+				%s = env.VAR
+			}
+		}
+	}
+}
+
+definitions {
+	backend "ref" {
+		origin = "http://localhost"
+	}
+}`
+
+	for _, attribute := range []string{
+		"disable_certificate_validation",
+		"disable_connection_reuse",
+		"http2",
+		"max_connections",
+	} {
+		_, err := configload.LoadBytes([]byte(fmt.Sprintf(config, attribute)), "test.hcl")
+		if err == nil {
+			t.Fatal("expected an error")
+		}
+
+		if !strings.HasSuffix(err.Error(),
+			fmt.Sprintf("backend reference: refinement for %q is not permitted; ", attribute)) {
+			t.Error(err)
+		}
+	}
+}
+
+func TestPrepareBackendRefineBlocks(t *testing.T) {
+	config := `server {
+	endpoint "/" {
+		request {
+			backend "ref" {
+				%s
+			}
+		}
+	}
+}
+
+definitions {
+	backend "ref" {
+		origin = "http://localhost"
+	}
+}`
+
+	_, err := configload.LoadBytes([]byte(fmt.Sprintf(config, `openapi { file = ""}`)), "test.hcl")
+	if err == nil {
+		t.Fatal("expected an error")
+	}
+
+	if !strings.HasSuffix(err.Error(),
+		fmt.Sprintf("backend reference: refinement for %q is not permitted; ", "openapi")) {
+		t.Error(err)
+	}
+}
diff --git a/config/configload/validate_test.go b/config/configload/validate_test.go
index ef0cd54a..016d6ba4 100644
--- a/config/configload/validate_test.go
+++ b/config/configload/validate_test.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/hashicorp/hcl/v2"
 
+	"github.com/avenga/couper/cache"
 	"github.com/avenga/couper/config/runtime"
 	logrustest "github.com/sirupsen/logrus/hooks/test"
 )
@@ -180,7 +181,10 @@ func TestLabels(t *testing.T) {
 		t.Run(tt.name, func(subT *testing.T) {
 			conf, err := LoadBytes([]byte(tt.hcl), "couper.hcl")
 			if conf != nil {
-				_, err = runtime.NewServerConfiguration(conf, log, nil)
+				tmpStoreCh := make(chan struct{})
+				defer close(tmpStoreCh)
+
+				_, err = runtime.NewServerConfiguration(conf, log, cache.New(log, tmpStoreCh))
 			}
 
 			var errMsg string
@@ -399,7 +403,10 @@ defaults {
 		t.Run(tt.name, func(subT *testing.T) {
 			conf, err := LoadBytes([]byte(tt.hcl), "couper.hcl")
 			if conf != nil {
-				_, err = runtime.NewServerConfiguration(conf, log, nil)
+				tmpStoreCh := make(chan struct{})
+				defer close(tmpStoreCh)
+
+				_, err = runtime.NewServerConfiguration(conf, log, cache.New(log, tmpStoreCh))
 			}
 
 			var errMsg string
diff --git a/config/runtime/access_control_test.go b/config/runtime/access_control_test.go
index 0d33b9cd..26f3408b 100644
--- a/config/runtime/access_control_test.go
+++ b/config/runtime/access_control_test.go
@@ -1,9 +1,10 @@
-package runtime
+package runtime_test
 
 import (
 	"testing"
 
 	"github.com/avenga/couper/config/configload"
+	"github.com/avenga/couper/config/runtime"
 	"github.com/avenga/couper/errors"
 )
 
@@ -92,7 +93,7 @@ func TestACDefinitions_errors(t *testing.T) {
 			if err != nil {
 				subT.Fatal(err)
 			}
-			_, err = NewServerConfiguration(cf, nil, nil)
+			_, err = runtime.NewServerConfiguration(cf, nil, nil)
 			if err == nil {
 				subT.Errorf("Expected error")
 			}
diff --git a/config/runtime/server_internal_test.go b/config/runtime/server_internal_test.go
index 475daa29..6d3d2d24 100644
--- a/config/runtime/server_internal_test.go
+++ b/config/runtime/server_internal_test.go
@@ -1,10 +1,13 @@
 package runtime
 
 import (
+	"context"
 	"reflect"
 	"testing"
-	"time"
 
+	logrustest "github.com/sirupsen/logrus/hooks/test"
+
+	"github.com/avenga/couper/cache"
 	"github.com/avenga/couper/config"
 	"github.com/avenga/couper/config/runtime/server"
 	"github.com/avenga/couper/eval"
@@ -208,7 +211,15 @@ func TestServer_validatePortHosts(t *testing.T) {
 			tt.args.conf.Context = eval.NewContext(nil, nil)
 			tt.args.conf.Settings = &config.DefaultSettings
 
-			if _, err := NewServerConfiguration(tt.args.conf, nil, nil); (err != nil) != tt.wantErr {
+			logger, _ := logrustest.NewNullLogger()
+			log := logger.WithContext(context.Background())
+
+			quitCh := make(chan struct{})
+			defer close(quitCh)
+
+			memStore := cache.New(log, quitCh)
+
+			if _, err := NewServerConfiguration(tt.args.conf, log, memStore); (err != nil) != tt.wantErr {
 				subT.Errorf("validatePortHosts() error = %v, wantErr %v", err, tt.wantErr)
 			}
 		})
@@ -240,24 +251,6 @@ func TestServer_GetCORS(t *testing.T) {
 	}
 }
 
-func TestServer_ParseDuration(t *testing.T) {
-	var target time.Duration
-
-	if err := parseDuration("non-duration", &target); err == nil {
-		t.Error("Unexpected NIL-error given")
-	}
-	if target != 0 {
-		t.Errorf("Unexpected duration given: %#v", target)
-	}
-
-	if err := parseDuration("1ms", &target); err != nil {
-		t.Errorf("Unexpected error given: %#v", err)
-	}
-	if target != 1000000 {
-		t.Errorf("Unexpected duration given: %#v", target)
-	}
-}
-
 func TestServer_ParseBodyLimit(t *testing.T) {
 	i, err := parseBodyLimit("non-size")
 	if err == nil {
diff --git a/eval/lib/oauth2_test.go b/eval/lib/oauth2_test.go
index c188aa9b..afc898b4 100644
--- a/eval/lib/oauth2_test.go
+++ b/eval/lib/oauth2_test.go
@@ -8,17 +8,15 @@ import (
 	"net/url"
 	"testing"
 
-	"github.com/hashicorp/hcl/v2"
-	"github.com/hashicorp/hcl/v2/hclsyntax"
 	"github.com/zclconf/go-cty/cty"
 
 	"github.com/avenga/couper/accesscontrol/jwk"
+	"github.com/avenga/couper/cache"
 	"github.com/avenga/couper/config"
-	hclbody "github.com/avenga/couper/config/body"
 	"github.com/avenga/couper/config/configload"
+	"github.com/avenga/couper/config/runtime"
 	"github.com/avenga/couper/eval"
 	"github.com/avenga/couper/eval/lib"
-	"github.com/avenga/couper/handler/transport"
 	"github.com/avenga/couper/internal/seetie"
 	"github.com/avenga/couper/internal/test"
 	"github.com/avenga/couper/oauth2/oidc"
@@ -27,14 +25,6 @@ import (
 func TestNewOAuthAuthorizationUrlFunction(t *testing.T) {
 	helper := test.New(t)
 
-	expFn := func(exp string) hcl.Expression {
-		e, diags := hclsyntax.ParseExpression([]byte(exp), "", hcl.InitialPos)
-		if diags.HasErrors() {
-			t.Fatal(diags)
-		}
-		return e
-	}
-
 	var origin *httptest.Server
 	origin = httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
 		var conf interface{}
@@ -50,104 +40,65 @@ func TestNewOAuthAuthorizationUrlFunction(t *testing.T) {
 		} else if req.URL.Path == "/jwks" {
 			conf = jwk.JWKSData{}
 		}
+
 		b, err := json.Marshal(conf)
 		helper.Must(err)
 		_, err = rw.Write(b)
 		helper.Must(err)
 	}))
-
-	backend := configload.NewBackend("origin", test.NewRemainContext("origin", origin.URL))
+	u := origin.URL + "/.well-known/openid-configuration"
+	backendConfig := ` server {}
+definitions {
+  oidc "auth-ref" {
+ 	client_id = "test-id"
+	client_secret = "test-s3cr3t"
+    configuration_url = "` + u + `"
+	redirect_uri = "${request.headers.x-want},${backend_requests.default.headers.x-want},${backend_responses.default.headers.x-want}"
+	verifier_value = "asdf"
+  }
+}
+`
 
 	log, _ := test.NewLogger()
 	logger := log.WithContext(context.Background())
 
-	tests := []struct {
-		name         string
-		oauth2Config *config.OIDC
-		want         string
-	}{
-		{
-			name: "redirect_uri with client request",
-			oauth2Config: &config.OIDC{
-				Name:             "auth-ref",
-				ConfigurationURL: origin.URL + "/.well-known/openid-configuration",
-				Remain: hclbody.New(&hcl.BodyContent{
-					Attributes: map[string]*hcl.Attribute{
-						lib.RedirectURI: {
-							Name: lib.RedirectURI,
-							Expr: expFn("request.headers.x-want"),
-						},
-					}})},
-			want: "https://couper.io/cb",
-		},
-		{
-			name: "redirect_uri with backend_requests", // works since client and backend request are the same at response obj
-			oauth2Config: &config.OIDC{
-				Name:             "auth-ref",
-				ConfigurationURL: origin.URL + "/.well-known/openid-configuration",
-				Remain: hclbody.New(&hcl.BodyContent{
-					Attributes: map[string]*hcl.Attribute{
-						lib.RedirectURI: {
-							Name: lib.RedirectURI,
-							Expr: expFn("backend_requests.default.headers.x-want"),
-						},
-					}})},
-			want: "https://couper.io/cb",
-		},
-		{
-			name: "redirect_uri with backend_responses",
-			oauth2Config: &config.OIDC{
-				Name:             "auth-ref",
-				ConfigurationURL: origin.URL + "/.well-known/openid-configuration",
-				Remain: hclbody.New(&hcl.BodyContent{
-					Attributes: map[string]*hcl.Attribute{
-						lib.RedirectURI: {
-							Name: lib.RedirectURI,
-							Expr: expFn("backend_responses.default.headers.x-want"),
-						},
-					}})},
-			want: "https://couper.io/cb",
-		},
-	}
+	couperConf, err := configload.LoadBytes([]byte(backendConfig), "test.hcl")
+	helper.Must(err)
 
-	for _, tt := range tests {
-		t.Run(tt.name, func(subT *testing.T) {
-			helper := test.New(subT)
+	quitCh := make(chan struct{}, 1)
+	defer close(quitCh)
+	memStore := cache.New(logger, quitCh)
 
-			req, err := http.NewRequest(http.MethodGet, "https://couper.io/", nil)
-			helper.Must(err)
-			*req = *req.Clone(context.Background())
-			req.Header.Set("x-want", tt.want)
+	_, err = runtime.NewServerConfiguration(couperConf, logger, memStore)
+	helper.Must(err)
 
-			res := &http.Response{
-				Header:     make(http.Header),
-				Request:    req,
-				StatusCode: http.StatusNoContent,
-			}
-			res.Header.Set("x-want", tt.want)
+	// redirect_uri = "${request.headers.x-want},${backend_requests.default.headers.x-want},${backend_responses.default.headers.x-want}"
+	want := "https://couper.io/cb,https://couper.io/cb,https://couper.io/cb"
 
-			tc := &transport.Config{}
-			conf, err := oidc.NewConfig(tt.oauth2Config, transport.NewBackend(backend.Config,
-				tc.With("http", "couper.io", "couper.io", ""),
-				&transport.BackendOptions{}, logger))
-			helper.Must(err)
+	req, rerr := http.NewRequest(http.MethodGet, "https://couper.io/", nil)
+	helper.Must(rerr)
+	req = req.Clone(context.Background())
+	req.Header.Set("x-want", "https://couper.io/cb")
 
-			ctx := eval.NewContext(nil, &config.Defaults{}).
-				WithOidcConfig(oidc.Configs{conf.Name: conf}).
-				WithClientRequest(req).
-				WithBeresp(res, false)
+	res := &http.Response{
+		Header:     http.Header{"x-want": []string{"https://couper.io/cb"}},
+		Request:    req,
+		StatusCode: http.StatusNoContent,
+	}
 
-			hclCtx := ctx.HCLContext()
-			val, err := hclCtx.Functions[lib.FnOAuthAuthorizationUrl].Call([]cty.Value{cty.StringVal("auth-ref")})
-			helper.Must(err)
+	hclCtx := couperConf.Context.(*eval.Context).
+		WithClientRequest(req).
+		WithBeresp(res, false).
+		HCLContext()
 
-			authUrl := seetie.ValueToString(val)
-			authUrlObj, err := url.Parse(authUrl)
-			helper.Must(err)
+	val, furr := hclCtx.Functions[lib.FnOAuthAuthorizationUrl].Call([]cty.Value{cty.StringVal("auth-ref")})
+	helper.Must(furr)
 
-			if authUrlObj.Query().Get(lib.RedirectURI) != tt.want {
-				subT.Errorf("Want: %v; got: %v", tt.want, val)
-			}
-		})
+	authUrl := seetie.ValueToString(val)
+	authUrlObj, perr := url.Parse(authUrl)
+	helper.Must(perr)
+
+	if value := authUrlObj.Query().Get(lib.RedirectURI); value != want {
+		t.Errorf("Want: %v; got: %v", want, value)
 	}
 }
diff --git a/handler/transport/backend_test.go b/handler/transport/backend_test.go
index c129c700..a4666724 100644
--- a/handler/transport/backend_test.go
+++ b/handler/transport/backend_test.go
@@ -12,10 +12,13 @@ import (
 	"time"
 
 	"github.com/hashicorp/hcl/v2"
+	"github.com/hashicorp/hcl/v2/hclsyntax"
 	"github.com/hashicorp/hcl/v2/hcltest"
 	logrustest "github.com/sirupsen/logrus/hooks/test"
+	"github.com/zclconf/go-cty/cty"
 
 	"github.com/avenga/couper/config"
+	hclbody "github.com/avenga/couper/config/body"
 	"github.com/avenga/couper/config/request"
 	"github.com/avenga/couper/errors"
 	"github.com/avenga/couper/eval"
@@ -34,17 +37,25 @@ func TestBackend_RoundTrip_Timings(t *testing.T) {
 	}))
 	defer origin.Close()
 
+	withTimingsFn := func(base hcl.Body, connect, ttfb, timeout string) hcl.Body {
+		content := &hcl.BodyContent{Attributes: map[string]*hcl.Attribute{
+			"connect_timeout": {Name: "connect_timeout", Expr: &hclsyntax.LiteralValueExpr{Val: cty.StringVal(connect)}},
+			"ttfb_timeout":    {Name: "ttfb_timeout", Expr: &hclsyntax.LiteralValueExpr{Val: cty.StringVal(ttfb)}},
+			"timeout":         {Name: "timeout", Expr: &hclsyntax.LiteralValueExpr{Val: cty.StringVal(timeout)}},
+		}}
+		return hclbody.MergeBodies(base, hclbody.New(content))
+	}
+
 	tests := []struct {
 		name        string
 		context     hcl.Body
-		tconf       *transport.Config
 		req         *http.Request
 		expectedErr string
 	}{
-		{"with zero timings", test.NewRemainContext("origin", origin.URL), &transport.Config{}, httptest.NewRequest(http.MethodGet, "http://1.2.3.4/", nil), ""},
-		{"with overall timeout", test.NewRemainContext("origin", origin.URL), &transport.Config{Timeout: time.Second / 2, ConnectTimeout: time.Minute}, httptest.NewRequest(http.MethodHead, "http://1.2.3.5/", nil), "deadline exceeded"},
-		{"with connect timeout", test.NewRemainContext("origin", "http://blackhole.webpagetest.org"), &transport.Config{ConnectTimeout: time.Second / 2}, httptest.NewRequest(http.MethodGet, "http://1.2.3.6/", nil), "i/o timeout"},
-		{"with ttfb timeout", test.NewRemainContext("origin", origin.URL), &transport.Config{TTFBTimeout: time.Second}, httptest.NewRequest(http.MethodHead, "http://1.2.3.7/", nil), "timeout awaiting response headers"},
+		{"with zero timings", test.NewRemainContext("origin", origin.URL), httptest.NewRequest(http.MethodGet, "http://1.2.3.4/", nil), ""},
+		{"with overall timeout", withTimingsFn(test.NewRemainContext("origin", origin.URL), "1m", "30s", "500ms"), httptest.NewRequest(http.MethodHead, "http://1.2.3.5/", nil), "deadline exceeded"},
+		{"with connect timeout", withTimingsFn(test.NewRemainContext("origin", "http://blackhole.webpagetest.org"), "750ms", "500ms", "1m"), httptest.NewRequest(http.MethodGet, "http://1.2.3.6/", nil), "i/o timeout"},
+		{"with ttfb timeout", withTimingsFn(test.NewRemainContext("origin", origin.URL), "10s", "1s", "1m"), httptest.NewRequest(http.MethodHead, "http://1.2.3.7/", nil), "timeout awaiting response headers"},
 	}
 
 	logger, hook := logrustest.NewNullLogger()
@@ -54,8 +65,7 @@ func TestBackend_RoundTrip_Timings(t *testing.T) {
 		t.Run(tt.name, func(subT *testing.T) {
 			hook.Reset()
 
-			tt.tconf.NoProxyFromEnv = true // use origin addr from transport.Config
-			backend := transport.NewBackend(tt.context, tt.tconf, nil, log)
+			backend := transport.NewBackend(tt.context, &transport.Config{NoProxyFromEnv: true}, nil, log)
 
 			_, err := backend.RoundTrip(tt.req)
 			if err != nil && tt.expectedErr == "" {
@@ -67,7 +77,7 @@ func TestBackend_RoundTrip_Timings(t *testing.T) {
 
 			if tt.expectedErr != "" &&
 				(err == nil || !isErr || !strings.HasSuffix(gerr.LogError(), tt.expectedErr)) {
-				subT.Errorf("Expected err %s, got: %v", tt.expectedErr, err)
+				subT.Errorf("Expected err %s, got: %#v", tt.expectedErr, err)
 			}
 		})
 	}
@@ -314,9 +324,9 @@ func TestBackend_director(t *testing.T) {
 		t.Run(tt.name, func(subT *testing.T) {
 			hclContext := helper.NewInlineContext(tt.inlineCtx)
 
-			backend := transport.NewBackend(hclContext, &transport.Config{
-				Timeout: time.Second,
-			}, nil, nullLog)
+			backend := transport.NewBackend(hclbody.MergeBodies(hclContext,
+				hclbody.New(hclbody.NewContentWithAttrName("timeout", "1s")),
+			), &transport.Config{}, nil, nullLog)
 
 			req := httptest.NewRequest(http.MethodGet, "https://example.com"+tt.path, nil)
 			*req = *req.WithContext(tt.ctx)
diff --git a/handler/transport/transport_test.go b/handler/transport/transport_test.go
new file mode 100644
index 00000000..8ac2072c
--- /dev/null
+++ b/handler/transport/transport_test.go
@@ -0,0 +1,15 @@
+package transport
+
+import (
+	"testing"
+	"time"
+)
+
+func Test_parseDuration(t *testing.T) {
+	var target time.Duration
+	parseDuration("1ms", &target)
+
+	if target != 1000000 {
+		t.Errorf("Unexpected duration given: %#v", target)
+	}
+}
diff --git a/internal/test/test_backend.go b/internal/test/test_backend.go
index 469ac34d..eb82436b 100644
--- a/internal/test/test_backend.go
+++ b/internal/test/test_backend.go
@@ -230,8 +230,9 @@ func jwks(rw http.ResponseWriter, req *http.Request) {
 
 func oidc(rw http.ResponseWriter, req *http.Request) {
 	body := []byte(`{
-			"issuer": "https://authorization.server",
 			"authorization_endpoint": "https://authorization.server/oauth2/authorize",
+			"issuer": "https://authorization.server",
+			"jwks_uri": "http://` + req.Host + `/jwks.json",
 			"token_endpoint": "http://` + req.Host + `/token",
 			"userinfo_endpoint": "http://` + req.Host + `/userinfo"
 			}`)
diff --git a/oauth2/oidc/config_test.go b/oauth2/oidc/config_test.go
index d3618b27..996380a9 100644
--- a/oauth2/oidc/config_test.go
+++ b/oauth2/oidc/config_test.go
@@ -9,9 +9,13 @@ import (
 	"testing"
 
 	"github.com/hashicorp/hcl/v2"
+	"github.com/hashicorp/hcl/v2/hclsyntax"
+	"github.com/zclconf/go-cty/cty"
 
 	"github.com/avenga/couper/accesscontrol/jwk"
 	"github.com/avenga/couper/config"
+	hclbody "github.com/avenga/couper/config/body"
+	"github.com/avenga/couper/config/configload"
 	"github.com/avenga/couper/handler/transport"
 	"github.com/avenga/couper/internal/test"
 	"github.com/avenga/couper/oauth2/oidc"
@@ -46,8 +50,25 @@ func TestConfig_Synced(t *testing.T) {
 	}))
 	defer origin.Close()
 
-	be := transport.NewBackend(hcl.EmptyBody(), &transport.Config{}, nil, logger)
-	o, err := oidc.NewConfig(&config.OIDC{ConfigurationURL: origin.URL + "/.well-known/openid-configuration"}, be)
+	oconf := &config.OIDC{
+		ConfigurationURL: origin.URL + "/.well-known/openid-configuration",
+		Remain: hclbody.New(&hcl.BodyContent{Attributes: map[string]*hcl.Attribute{
+			"redirect_uri":   {Name: "redirect_uri", Expr: &hclsyntax.LiteralValueExpr{Val: cty.StringVal("")}},
+			"verifier_value": {Name: "verifier_value", Expr: &hclsyntax.LiteralValueExpr{Val: cty.StringVal("")}},
+		}}),
+	}
+	// configload internals here... TODO: integration test?
+	err := oconf.Prepare(func(attr string, val string, body config.Inline) (hcl.Body, error) {
+		return configload.PrepareBackend(nil, attr, val, body)
+	})
+	helper.Must(err)
+
+	backends := make(map[string]http.RoundTripper)
+	for k, b := range oconf.Backends {
+		backends[k] = transport.NewBackend(b, &transport.Config{}, nil, logger)
+	}
+
+	o, err := oidc.NewConfig(oconf, backends)
 	helper.Must(err)
 
 	wg := sync.WaitGroup{}
diff --git a/server/http_backend_test.go b/server/http_backend_test.go
index 9b8d6fb3..fbc29377 100644
--- a/server/http_backend_test.go
+++ b/server/http_backend_test.go
@@ -6,12 +6,72 @@ import (
 	"io"
 	"net/http"
 	"net/http/httptest"
+	"sync"
 	"testing"
 	"time"
 
 	"github.com/avenga/couper/internal/test"
 )
 
+func TestBackend_MaxConnections(t *testing.T) {
+	helper := test.New(t)
+
+	const reqCount = 3
+	lastSeen := map[string]string{}
+	lastSeenMu := sync.Mutex{}
+	origin := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
+		lastSeenMu.Lock()
+		defer lastSeenMu.Unlock()
+
+		if lastSeen[r.URL.Path] != "" && lastSeen[r.URL.Path] != r.RemoteAddr {
+			t.Errorf("expected same remote addr for path: %q", r.URL.Path)
+			rw.WriteHeader(http.StatusInternalServerError)
+		} else {
+			rw.WriteHeader(http.StatusNoContent)
+		}
+		lastSeen[r.URL.Path] = r.RemoteAddr
+	}))
+
+	defer origin.Close()
+
+	shutdown, _ := newCouperWithTemplate("testdata/integration/backends/03_couper.hcl", helper, map[string]interface{}{
+		"origin": origin.URL,
+	})
+	defer shutdown()
+
+	paths := []string{
+		"/",
+		"/be",
+		"/fake-sequence",
+	}
+
+	originWait := sync.WaitGroup{}
+	originWait.Add(len(paths) * reqCount)
+	waitForCh := make(chan struct{})
+
+	client := test.NewHTTPClient()
+
+	for _, clientPath := range paths {
+		for i := 0; i < reqCount; i++ {
+			go func(path string) {
+				req, _ := http.NewRequest(http.MethodGet, "http://couper.dev:8080"+path, nil)
+				<-waitForCh
+				res, err := client.Do(req)
+				helper.Must(err)
+
+				if res.StatusCode != http.StatusNoContent {
+					t.Errorf("want: 204, got %d", res.StatusCode)
+				}
+
+				originWait.Done()
+			}(clientPath)
+		}
+	}
+
+	close(waitForCh)
+	originWait.Wait()
+}
+
 func TestBackend_MaxConnections_BodyClose(t *testing.T) {
 	helper := test.New(t)
 
diff --git a/server/http_custom_logs_test.go b/server/http_custom_logs_test.go
index 72f8fc26..2dc3ec52 100644
--- a/server/http_custom_logs_test.go
+++ b/server/http_custom_logs_test.go
@@ -10,6 +10,7 @@ import (
 	"github.com/sirupsen/logrus"
 
 	"github.com/avenga/couper/internal/test"
+	"github.com/avenga/couper/logging"
 )
 
 func TestCustomLogs_Upstream(t *testing.T) {
@@ -19,9 +20,9 @@ func TestCustomLogs_Upstream(t *testing.T) {
 	defer shutdown()
 
 	type testCase struct {
-		path  string
-		expAL logrus.Fields
-		expUL logrus.Fields
+		path        string
+		expAccess   logrus.Fields
+		expUpstream logrus.Fields
 	}
 
 	hmacToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwic2NvcGUiOiJmb28gYmFyIiwiaWF0IjoxNTE2MjM5MDIyfQ.7wz7Z7IajfEpwYayfshag6tQVS0e0zZJyjAhuFC0L-E"
@@ -55,12 +56,12 @@ func TestCustomLogs_Upstream(t *testing.T) {
 		{
 			"/backend",
 			logrus.Fields{"api": "couper test-backend", "server": "couper test-backend"},
-			logrus.Fields{"backend": string("couper test-backend")},
+			logrus.Fields{"backend": "couper test-backend"},
 		},
 		{
 			"/jwt-valid",
 			logrus.Fields{"jwt_regular": "GET", "server": "couper test-backend"},
-			logrus.Fields{"backend": string("couper test-backend")},
+			logrus.Fields{"backend": "couper test-backend"},
 		},
 	} {
 		t.Run(tc.path, func(st *testing.T) {
@@ -75,29 +76,40 @@ func TestCustomLogs_Upstream(t *testing.T) {
 			helper.Must(err)
 
 			// Wait for logs
-			time.Sleep(200 * time.Millisecond)
+			time.Sleep(100 * time.Millisecond)
 
-			// Access log
 			entries := hook.AllEntries()
-			if len(entries) < 2 {
-				st.Fatal("Expected logs, got nothing")
+			var accessLog, upstreamLog logrus.Fields
+			for _, entry := range entries {
+				request := entry.Data["request"].(logging.Fields)
+				path, _ := request["path"].(string)
+				if entry.Data["type"] == "couper_access" {
+					accessLog = entry.Data
+				} else if entry.Data["type"] == "couper_backend" && path != "/jwks.json" {
+					upstreamLog = entry.Data
+				}
 			}
 
-			got, ok := entries[1].Data["custom"].(logrus.Fields)
-			if !ok {
-				st.Fatalf("expected\n%#v\ngot\n%#v", tc.expAL, got)
+			if accessLog == nil || upstreamLog == nil {
+				st.Fatalf("expected logs, got access: %p, got upstream: %p", accessLog, upstreamLog)
 			}
-			if !reflect.DeepEqual(tc.expAL, got) {
-				st.Errorf("expected\n%#v\ngot\n%#v", tc.expAL, got)
+
+			customAccess, ok := accessLog["custom"].(logrus.Fields)
+			if !ok {
+				st.Fatal("expected access log custom field")
 			}
 
-			// Upstream log
-			got, ok = entries[0].Data["custom"].(logrus.Fields)
+			customUpstream, ok := upstreamLog["custom"].(logrus.Fields)
 			if !ok {
-				st.Fatalf("expected\n%#v\ngot\n%#v", tc.expUL, got)
+				st.Fatal("expected upstream log custom field")
+			}
+
+			if !cmp.Equal(tc.expAccess, customAccess) {
+				st.Error(cmp.Diff(tc.expAccess, customAccess))
 			}
-			if !cmp.Equal(tc.expUL, got) {
-				st.Error(cmp.Diff(tc.expUL, got))
+
+			if !cmp.Equal(tc.expUpstream, customUpstream) {
+				st.Error(cmp.Diff(tc.expUpstream, customUpstream))
 			}
 		})
 	}
diff --git a/server/http_endpoints_test.go b/server/http_endpoints_test.go
index da9acde8..0701776e 100644
--- a/server/http_endpoints_test.go
+++ b/server/http_endpoints_test.go
@@ -32,7 +32,7 @@ func TestBackend_BackendVariable_RequestResponse(t *testing.T) {
 	client := newClient()
 	helper := test.New(t)
 
-	shutdown, hook := newCouper("testdata/integration/backend/01_couper.hcl", helper)
+	shutdown, hook := newCouper("testdata/integration/backends/02_couper.hcl", helper)
 	defer shutdown()
 
 	req, err := http.NewRequest(http.MethodGet, "http://example.com:8080/request", nil)
@@ -58,32 +58,43 @@ func TestBackend_BackendVariable_RequestResponse(t *testing.T) {
 			continue
 		}
 
-		responseHeaders := entry.Data["response"].(logging.Fields)["headers"].(map[string]string)
-		data := entry.Data["custom"].(logrus.Fields)
+		responseLogs, _ := entry.Data["response"].(logging.Fields)
+		data, _ := entry.Data["custom"].(logrus.Fields)
+
+		if data != nil && entry.Data["url"] == "http://localhost:8081/token" {
+			expected := logrus.Fields{
+				"x-from-request-body":       "grant_type=client_credentials",
+				"x-from-request-form-body":  "client_credentials",
+				"x-from-request-header":     "Basic cXBlYjpiZW4=",
+				"x-from-response-header":    "60s",
+				"x-from-response-body":      `{"access_token":"the_access_token","expires_in":60}`,
+				"x-from-response-json-body": "the_access_token",
+			}
+			expectedHeaders := map[string]string{
+				"content-type": "application/json",
+				"location":     "Basic cXBlYjpiZW4=|client_credentials|60s|the_access_token",
+			}
 
-		url := entry.Data["url"]
-		if url == "http://localhost:8081/token" {
-			if data["x-from-request-body"] != "grant_type=client_credentials" ||
-				data["x-from-request-form-body"] != "client_credentials" ||
-				data["x-from-request-header"] != "Basic cXBlYjpiZW4=" ||
-				data["x-from-response-header"] != "60s" ||
-				data["x-from-response-body"] != `{"access_token":"the_access_token","expires_in":60}` ||
-				data["x-from-response-json-body"] != "the_access_token" {
-				t.Errorf("Unexpected logs given: %#v", data)
+			if diff := cmp.Diff(data, expected); diff != "" {
+				t.Error(diff)
 			}
-			if responseHeaders["location"] != "Basic cXBlYjpiZW4=|client_credentials|60s|the_access_token" {
-				t.Errorf("Unexpected responseHeaders given: %#v", responseHeaders)
+
+			if diff := cmp.Diff(responseLogs["headers"], expectedHeaders); diff != "" {
+				t.Error(diff)
 			}
 		} else {
-			if data["x-from-request-json-body"] != float64(1) ||
-				data["x-from-request-header"] != "bar" ||
-				data["x-from-requests-json-body"] != float64(1) ||
-				data["x-from-requests-header"] != "bar" ||
-				data["x-from-response-header"] != "application/json" ||
-				data["x-from-response-json-body"] != "/anything" ||
-				data["x-from-responses-header"] != "application/json" ||
-				data["x-from-responses-json-body"] != "/anything" {
-				t.Errorf("Unexpected logs given: %#v", data)
+			expected := logrus.Fields{
+				"x-from-request-json-body":   float64(1),
+				"x-from-request-header":      "bar",
+				"x-from-requests-json-body":  float64(1),
+				"x-from-requests-header":     "bar",
+				"x-from-response-header":     "application/json",
+				"x-from-response-json-body":  "/anything",
+				"x-from-responses-header":    "application/json",
+				"x-from-responses-json-body": "/anything",
+			}
+			if diff := cmp.Diff(data, expected); diff != "" {
+				t.Error(diff)
 			}
 		}
 	}
@@ -93,7 +104,7 @@ func TestBackend_BackendVariable(t *testing.T) {
 	client := newClient()
 	helper := test.New(t)
 
-	shutdown, hook := newCouper("testdata/integration/backend/01_couper.hcl", helper)
+	shutdown, hook := newCouper("testdata/integration/backends/02_couper.hcl", helper)
 	defer shutdown()
 
 	req, err := http.NewRequest(http.MethodGet, "http://example.com:8080/", nil)
@@ -779,8 +790,7 @@ func TestEndpointSequenceBackendTimeout(t *testing.T) {
 		}
 
 		path := entry.Data["request"].(logging.Fields)["path"]
-
-		if entry.Message == "backend timeout error: deadline exceeded" {
+		if entry.Message == "backend timeout error: anonymous_3_23: deadline exceeded" {
 			ctxDeadlineSeen = true
 			if path != "/" {
 				t.Errorf("expected '/' to fail")
@@ -832,6 +842,14 @@ func TestEndpointErrorHandler(t *testing.T) {
 
 	shutdown, hook := newCouper(filepath.Join(testdataPath, "14_couper.hcl"), helper)
 	defer shutdown()
+	defer func() {
+		if !t.Failed() {
+			return
+		}
+		for _, e := range hook.AllEntries() {
+			t.Logf("%#v", e.Data)
+		}
+	}()
 
 	type testcase struct {
 		name              string
diff --git a/server/http_error_handler_test.go b/server/http_error_handler_test.go
index cfeb965c..e7d0a50d 100644
--- a/server/http_error_handler_test.go
+++ b/server/http_error_handler_test.go
@@ -1,16 +1,13 @@
 package server_test
 
 import (
-	"context"
 	"net/http"
 	"strings"
 	"testing"
 
-	logrustest "github.com/sirupsen/logrus/hooks/test"
 	"github.com/zclconf/go-cty/cty"
 
 	"github.com/avenga/couper/accesscontrol/jwt"
-	"github.com/avenga/couper/command"
 	"github.com/avenga/couper/config"
 	"github.com/avenga/couper/config/configload"
 	"github.com/avenga/couper/eval"
@@ -115,18 +112,12 @@ func TestAccessControl_ErrorHandler_BasicAuth_Wildcard(t *testing.T) {
 }
 
 func TestAccessControl_ErrorHandler_Configuration_Error(t *testing.T) {
-	helper := test.New(t)
-	couperConfig, err := configload.LoadFiles("testdata/integration/error_handler/03_couper.hcl", "")
-	helper.Must(err)
-
-	log, _ := logrustest.NewNullLogger()
-	ctx := context.TODO()
+	_, err := configload.LoadFiles("testdata/integration/error_handler/03_couper.hcl", "")
 
-	expectedMsg := "03_couper.hcl:24,5-11: Missing required argument; The argument \"grant_type\" is required, but was not set."
+	expectedMsg := "03_couper.hcl:24,12-12: Missing required argument; The argument \"grant_type\" is required, but no definition was found."
 
-	err = command.NewRun(ctx).Execute([]string{couperConfig.Filename}, couperConfig, log.WithContext(ctx))
 	if err == nil {
-		t.Error("logErr should not be nil")
+		t.Error("config error should not be nil")
 	} else if !strings.HasSuffix(err.Error(), expectedMsg) {
 		t.Errorf("\nwant:\t%s\ngot:\t%v", expectedMsg, err.Error())
 	}
diff --git a/server/http_integration_test.go b/server/http_integration_test.go
index c5feab99..08cdc592 100644
--- a/server/http_integration_test.go
+++ b/server/http_integration_test.go
@@ -135,6 +135,7 @@ func newCouperWithConfig(couperConfig *config.Couper, helper *test.Helper) (func
 	ctx, cancelFn := context.WithCancel(context.Background())
 	shutdownFn := func() {
 		if helper.TestFailed() { // log on error
+			time.Sleep(time.Second)
 			for _, entry := range hook.AllEntries() {
 				s, _ := entry.String()
 				helper.Logf(s)
@@ -156,14 +157,18 @@ func newCouperWithConfig(couperConfig *config.Couper, helper *test.Helper) (func
 		if err := command.NewRun(ctx).Execute([]string{couperConfig.Filename}, couperConfig, log.WithContext(ctx)); err != nil {
 			command.RunCmdTestCallback()
 			shutdownFn()
-			panic(err)
+			if lerr, ok := err.(*errors.Error); ok {
+				panic(lerr.LogError())
+			} else {
+				panic(err)
+			}
 		}
 	}()
 	<-waitForCh
 
 	for _, entry := range hook.AllEntries() {
 		if entry.Level < logrus.WarnLevel {
-			defer cleanup(cancelFn, helper) // ok in loop, next line is the end
+			defer os.Exit(1) // ok in loop, next line is the end
 			helper.Must(fmt.Errorf("error: %#v: %s", entry.Data, entry.Message))
 		}
 	}
@@ -1162,6 +1167,94 @@ func TestHTTPServer_Backends_Reference(t *testing.T) {
 	}
 }
 
+func TestHTTPServer_Backends_Reference_BasicAuth(t *testing.T) {
+	client := newClient()
+
+	configPath := "testdata/integration/config/13_couper.hcl"
+
+	helper := test.New(t)
+	shutdown, _ := newCouper(configPath, helper)
+	defer shutdown()
+
+	type testcase struct {
+		path     string
+		wantAuth bool
+	}
+
+	for _, tc := range []testcase{
+		{"/", false},
+		{"/granted", true},
+	} {
+		req, err := http.NewRequest(http.MethodGet, "http://localhost:8080"+tc.path, nil)
+		helper.Must(err)
+
+		res, err := client.Do(req)
+		helper.Must(err)
+
+		b, err := io.ReadAll(res.Body)
+		helper.Must(err)
+
+		helper.Must(res.Body.Close())
+
+		type result struct {
+			Headers http.Header
+		}
+		r := result{}
+		helper.Must(json.Unmarshal(b, &r))
+
+		if tc.wantAuth && !strings.HasPrefix(r.Headers.Get("Authorization"), "Basic ") {
+			t.Error("expected Authorization header value")
+		}
+	}
+}
+
+func TestHTTPServer_Backends_Reference_PathPrefix(t *testing.T) {
+	client := newClient()
+
+	configPath := "testdata/integration/config/12_couper.hcl"
+
+	helper := test.New(t)
+	shutdown, _ := newCouper(configPath, helper)
+	defer shutdown()
+
+	type testcase struct {
+		path       string
+		wantPath   string
+		wantStatus int
+	}
+
+	for _, tc := range []testcase{
+		{"/", "/anything", http.StatusOK},
+		{"/prefixed", "/my-prefix/anything", http.StatusNotFound},
+	} {
+		req, err := http.NewRequest(http.MethodGet, "http://localhost:8080"+tc.path, nil)
+		helper.Must(err)
+
+		res, err := client.Do(req)
+		helper.Must(err)
+
+		type result struct {
+			Path string
+		}
+
+		b, err := io.ReadAll(res.Body)
+		helper.Must(err)
+
+		helper.Must(res.Body.Close())
+
+		r := result{}
+		helper.Must(json.Unmarshal(b, &r))
+
+		if res.StatusCode != tc.wantStatus {
+			t.Errorf("expected status: %d, got %d", tc.wantStatus, res.StatusCode)
+		}
+
+		if r.Path != tc.wantPath {
+			t.Errorf("expected path: %q, got: %q", tc.wantPath, r.Path)
+		}
+	}
+}
+
 func TestHTTPServer_OriginVsURL(t *testing.T) {
 	client := newClient()
 
@@ -1816,7 +1909,7 @@ func TestHTTPServer_Endpoint_Evaluation(t *testing.T) {
 			jsonResult.Origin = res.Header.Get("X-Origin")
 
 			if !reflect.DeepEqual(jsonResult, tc.exp) {
-				subT.Errorf("want: %#v, got: %#v, payload:\n%s", tc.exp, jsonResult, string(resBytes))
+				subT.Errorf("\nwant:\t%#v\ngot:\t%#v\npayload:\n%s", tc.exp, jsonResult, string(resBytes))
 			}
 		})
 	}
@@ -2843,25 +2936,6 @@ func TestHTTPServer_Endpoint_Evaluation_Inheritance(t *testing.T) {
 	}
 }
 
-func TestHTTPServer_Endpoint_Evaluation_Inheritance_Backend_Block(t *testing.T) {
-	helper := test.New(t)
-	client := newClient()
-
-	shutdown, _ := newCouper("testdata/integration/endpoint_eval/08_couper.hcl", test.New(t))
-	defer shutdown()
-
-	req, err := http.NewRequest(http.MethodGet, "http://example.com:8080/"+
-		strings.Replace(testBackend.Addr(), "http://", "", 1), nil)
-	helper.Must(err)
-
-	res, err := client.Do(req)
-	helper.Must(err)
-
-	if res.StatusCode != http.StatusBadRequest {
-		t.Error("Expected a bad request without required query param")
-	}
-}
-
 func TestOpenAPIValidateConcurrentRequests(t *testing.T) {
 	helper := test.New(t)
 	client := newClient()
@@ -3190,7 +3264,13 @@ func TestAPICatchAll(t *testing.T) {
 
 func Test_LoadAccessControl(t *testing.T) {
 	// Tests the config load with ACs and "error_handler" blocks...
-	shutdown, _ := newCouper("testdata/integration/config/07_couper.hcl", test.New(t))
+	backend := test.NewBackend()
+	defer backend.Close()
+
+	shutdown, _ := newCouperWithTemplate("testdata/integration/config/07_couper.hcl", test.New(t), map[string]interface{}{
+		"asOrigin": backend.Addr(),
+	})
+
 	test.WaitForOpenPort(8080)
 	shutdown()
 }
@@ -3240,7 +3320,6 @@ func TestJWTAccessControl(t *testing.T) {
 		{"local RSA JWKS without kid", "/jwks/rsa", http.Header{"Authorization": []string{"Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEyMzQ1Njc4OTB9.V9skZUql-mHqwOzVdzamqAOWSx8fjEA-6py0nfxLRSl7h1bQvqUCWMZUAkMJK6RuJ3y5YAr8ZBXZsh4rwABp_3hitQitMXnV6nr5qfzVDE9-mdS4--Bj46-JlkHacNcK24qlnn_EXGJlzCj6VFgjObSy6geaTY9iDVF6EzjZkxc1H75XRlNYAMu-0KCGfKdte0qASeBKrWnoFNEpnXZ_jhqRRNVkaSBj7_HPXD6oPqKBQf6Jh6fGgdz6q4KNL-t-Qa2_eKc8tkrYNdTdxco-ufmmLiUQ_MzRAqowHb2LdsFJP9rN2QT8MGjRXqGvkCd0EsLfqAeCPkTXs1kN8LGlvw"}}, "", http.StatusForbidden, "", `access control error: JWKS: token is unverifiable: Keyfunc returned an error`},
 		{"local RSA JWKS with unsupported kid", "/jwks/rsa", http.Header{"Authorization": []string{"Bearer eyJraWQiOiJyczI1Ni11bnN1cHBvcnRlZCIsImFsZyI6IlJTMjU2IiwidHlwIjoiSldUIn0.eyJzdWIiOjEyMzQ1Njc4OTB9.wx1MkMgJhh6gnOvvrnnkRpEUDe-0KpKWw9ZIfDVHtGkuL46AktBgfbaW1ttB78wWrIW9OPfpLqKwkPizwfShoXKF9qN-6TlhPSWIUh0_kBHEj7H4u45YZXH1Ha-r9kGzly1PmLx7gzxUqRpqYnwo0TzZSEr_a8rpfWaC0ZJl3CKARormeF3tzW_ARHnGUqck4VjPfX50Ot6B5nool6qmsCQLLmDECIKBDzZicqdeWH7JPvRZx45R5ZHJRQpD3Z2iqVIF177Wj1C8q75Gxj2PXziIVKplmIUrKN-elYj3kBtJkDFneb384FPLuzsQZOR6HQmKXG2nA1WOfsblJSz3FA"}}, "", http.StatusForbidden, "", "access control error: JWKS: token is unverifiable: Keyfunc returned an error"},
 		{"local RSA JWKS with non-parsable cert", "/jwks/rsa", http.Header{"Authorization": []string{"Bearer eyJraWQiOiJyczI1Ni13cm9uZy1jZXJ0IiwiYWxnIjoiUlMyNTYiLCJ0eXAiOiJKV1QifQ.eyJzdWIiOjEyMzQ1Njc4OTB9.n--6mjzfnPKbaYAquBK3v6gsbmvEofSprk3jwWGSKPdDt2VpVOe8ZNtGhJj_3f1h86-wg-gEQT5GhJmsI47X9MJ70j74dqhXUF6w4782OljstP955whuSM9hJAIvUw_WV1sqtkiESA-CZiNJIBydL5YzV2nO3gfEYdy9EdMJ2ykGLRBajRxhShxsfaZykFKvvWpy1LbUc-gfRZ4q8Hs9B7b_9RGdbpRwBtwiqPPzhjC5O86vk7ZoiG9Gq7pg52yEkLqdN4a5QkfP8nNeTTMAsqPQL1-1TAC7rIGekoUtoINRR-cewPpZ_E7JVxXvBVvPe3gX_2NzGtXkLg5QDt6RzQ"}}, "", http.StatusForbidden, "", "access control error: JWKS: token is unverifiable: Keyfunc returned an error"},
-		{"local RSA JWKS not found", "/jwks/rsa/not_found", http.Header{"Authorization": []string{"Bearer " + rsaToken}}, "", http.StatusForbidden, "", "access control error: JWKS_not_found: token is unverifiable: Keyfunc returned an error"},
 		{"local RSA JWKS", "/jwks/rsa", http.Header{"Authorization": []string{"Bearer " + rsaToken}}, "", http.StatusOK, "", ""},
 		{"local RSA JWKS with scope", "/jwks/rsa/scope", http.Header{"Authorization": []string{"Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6InJzMjU2IiwidHlwIjoiSldUIn0.eyJzdWIiOjEyMzQ1Njc4OTAsInNjb3BlIjpbImZvbyIsImJhciJdfQ.IFqIF_9ELXl3A-oy52G0Sg5f34ah3araOxFboskEw110nXdb_-UuxCnG0naFVFje7xvNrGbJgVAbBRX1v1I_to4BR8RzvIh2hi5IgBmqclIYsYbVWlEhsvjBhFR2b90Rz0APUdfgHp-nvgLB13jxm8f4TRr4ZDnvUQdZp3vI5PMj9optEmlZvexkNLDQLrBvoGCfVHodZyPQMLNVKp0TXWksPT-bw0E7Lq1GeYe2eU0GwHx8fugo2-v44dfCp0RXYYG6bI_Z-U3KZpvdj05n2_UDgTJFFm4c5i9UjILvlO73QJpMNi5eBjerm2alTisSCoiCtfgIgVsM8yHoomgarg"}}, "", http.StatusOK, `["foo","bar"]`, ""},
 		{"remote RSA JWKS x5c", "/jwks/rsa/remote", http.Header{"Authorization": []string{"Bearer " + rsaToken}}, "", http.StatusOK, "", ""},
@@ -4482,6 +4561,7 @@ func TestOIDCPKCEFunctions(t *testing.T) {
 
 	oauthOrigin := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
 		if req.URL.Path == "/.well-known/openid-configuration" {
+			rw.Header().Set("Content-Type", "Application/json")
 			body := []byte(`{
 			"issuer": "https://authorization.server",
 			"authorization_endpoint": "https://authorization.server/oauth2/authorize",
@@ -4491,7 +4571,11 @@ func TestOIDCPKCEFunctions(t *testing.T) {
 			}`)
 			_, werr := rw.Write(body)
 			helper.Must(werr)
-
+			return
+		} else if req.URL.Path == "/jwks" {
+			rw.Header().Set("Content-Type", "Application/json")
+			_, werr := rw.Write([]byte(`{}`))
+			helper.Must(werr)
 			return
 		}
 		rw.WriteHeader(http.StatusBadRequest)
@@ -4563,6 +4647,11 @@ func TestOIDCNonceFunctions(t *testing.T) {
 			_, werr := rw.Write(body)
 			helper.Must(werr)
 
+			return
+		} else if req.URL.Path == "/jwks" {
+			rw.Header().Set("Content-Type", "Application/json")
+			_, werr := rw.Write([]byte(`{}`))
+			helper.Must(werr)
 			return
 		}
 		rw.WriteHeader(http.StatusBadRequest)
@@ -4628,9 +4717,14 @@ func TestOIDCDefaultPKCEFunctions(t *testing.T) {
 			}`)
 			_, werr := rw.Write(body)
 			helper.Must(werr)
-
+			return
+		} else if req.URL.Path == "/jwks" {
+			rw.Header().Set("Content-Type", "Application/json")
+			_, werr := rw.Write([]byte(`{}`))
+			helper.Must(werr)
 			return
 		}
+
 		rw.WriteHeader(http.StatusBadRequest)
 	}))
 	defer oauthOrigin.Close()
@@ -4693,7 +4787,11 @@ func TestOIDCDefaultNonceFunctions(t *testing.T) {
 			}`)
 			_, werr := rw.Write(body)
 			helper.Must(werr)
-
+			return
+		} else if req.URL.Path == "/jwks" {
+			rw.Header().Set("Content-Type", "Application/json")
+			_, werr := rw.Write([]byte(`{}`))
+			helper.Must(werr)
 			return
 		}
 		rw.WriteHeader(http.StatusBadRequest)
diff --git a/server/http_oauth2_test.go b/server/http_oauth2_test.go
index 26205713..850cf908 100644
--- a/server/http_oauth2_test.go
+++ b/server/http_oauth2_test.go
@@ -8,6 +8,7 @@ import (
 	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
+	"reflect"
 	"strings"
 	"sync"
 	"sync/atomic"
@@ -19,6 +20,7 @@ import (
 
 	"github.com/avenga/couper/eval/lib"
 	"github.com/avenga/couper/internal/test"
+	"github.com/avenga/couper/logging"
 	"github.com/avenga/couper/oauth2"
 )
 
@@ -202,12 +204,16 @@ func TestEndpoints_OAuth2_Options(t *testing.T) {
 
 func TestOAuth2_AccessControl(t *testing.T) {
 	client := newClient()
-	helper := test.New(t)
 
 	st := "qeirtbnpetrbi"
 	state := oauth2.Base64urlSha256(st)
 
 	oauthOrigin := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
+		errResp := func(err error) {
+			rw.WriteHeader(http.StatusInternalServerError)
+			_, _ = rw.Write([]byte(err.Error()))
+		}
+
 		if req.URL.Path == "/token" {
 			if accept := req.Header.Get("Accept"); accept != "application/json" {
 				t.Errorf("expected Accept %q, got: %q", "application/json", accept)
@@ -272,17 +278,30 @@ func TestOAuth2_AccessControl(t *testing.T) {
 					mapClaims["nonce"] = nonce
 				}
 				keyBytes, err := ioutil.ReadFile("testdata/integration/files/pkcs8.key")
-				helper.Must(err)
+				if err != nil {
+					errResp(err)
+					return
+				}
+
 				key, parseErr := jwt.ParseRSAPrivateKeyFromPEM(keyBytes)
-				helper.Must(parseErr)
+				if parseErr != nil {
+					errResp(err)
+					return
+				}
+
 				var kid string
 				if strings.HasSuffix(code, "-wkid-id") {
 					kid = "not-found"
 				} else {
 					kid = "rs256"
 				}
+
 				idToken, err := lib.CreateJWT("RS256", key, mapClaims, map[string]interface{}{"kid": kid})
-				helper.Must(err)
+				if err != nil {
+					errResp(err)
+					return
+				}
+
 				idTokenToAdd = `"id_token":"` + idToken + `",
 				`
 			}
@@ -296,21 +315,30 @@ func TestOAuth2_AccessControl(t *testing.T) {
 				"authorization": "` + req.Header.Get("Authorization") + `"
 			}`)
 			_, werr := rw.Write(body)
-			helper.Must(werr)
+			if werr != nil {
+				t.Log(werr)
+			}
 
 			return
 		} else if req.URL.Path == "/userinfo" {
 			body := []byte(`{"sub": "myself"}`)
 			_, werr := rw.Write(body)
-			helper.Must(werr)
+			if werr != nil {
+				t.Log(werr)
+			}
 
 			return
 		} else if req.URL.Path == "/jwks" {
 			jsonBytes, rerr := ioutil.ReadFile("testdata/integration/files/jwks.json")
-			helper.Must(rerr)
+			if rerr != nil {
+				errResp(rerr)
+				return
+			}
 			b := bytes.NewBuffer(jsonBytes)
 			_, werr := b.WriteTo(rw)
-			helper.Must(werr)
+			if werr != nil {
+				t.Log(werr)
+			}
 
 			return
 		} else if req.URL.Path == "/.well-known/openid-configuration" {
@@ -322,8 +350,9 @@ func TestOAuth2_AccessControl(t *testing.T) {
 			"userinfo_endpoint": "http://` + req.Host + `/userinfo"
 			}`)
 			_, werr := rw.Write(body)
-			helper.Must(werr)
-
+			if werr != nil {
+				t.Log(werr)
+			}
 			return
 		}
 		rw.WriteHeader(http.StatusBadRequest)
@@ -378,11 +407,11 @@ func TestOAuth2_AccessControl(t *testing.T) {
 		{"code; nonce param; relative redirect_uri", "09_couper.hcl", http.MethodGet, "/cb?code=qeuboub-id", http.Header{"Cookie": []string{"nnc=" + st}, "X-Forwarded-Proto": []string{"https"}, "X-Forwarded-Host": []string{"www.example.com"}}, http.StatusOK, "code=qeuboub-id&grant_type=authorization_code&redirect_uri=https%3A%2F%2Fwww.example.com%2Fcb", "Basic Zm9vOmV0YmluYnA0aW4=", ""},
 	} {
 		t.Run(tc.path[1:], func(subT *testing.T) {
-			shutdown, hook := newCouperWithTemplate("testdata/oauth2/"+tc.filename, test.New(t), map[string]interface{}{"asOrigin": oauthOrigin.URL})
-			defer shutdown()
-
 			h := test.New(subT)
 
+			shutdown, hook := newCouperWithTemplate("testdata/oauth2/"+tc.filename, h, map[string]interface{}{"asOrigin": oauthOrigin.URL})
+			defer shutdown()
+
 			req, err := http.NewRequest(tc.method, "http://back.end:8080"+tc.path, nil)
 			h.Must(err)
 
@@ -444,6 +473,10 @@ func TestOAuth2_AC_Backend(t *testing.T) {
 	asOrigin := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
 		sub := req.Header.Get("X-Sub")
 		if req.URL.Path == "/token" {
+			if req.Method != http.MethodPost {
+				rw.WriteHeader(http.StatusMethodNotAllowed)
+				return
+			}
 			rw.Header().Set("Content-Type", "application/json")
 			rw.WriteHeader(http.StatusOK)
 			mapClaims := jwt.MapClaims{
@@ -473,12 +506,14 @@ func TestOAuth2_AC_Backend(t *testing.T) {
 
 			return
 		} else if req.URL.Path == "/userinfo" {
+			rw.Header().Set("Content-Type", "application/json")
 			body := []byte(`{"sub": "` + sub + `"}`)
 			_, werr := rw.Write(body)
 			helper.Must(werr)
 
 			return
 		} else if req.URL.Path == "/jwks" {
+			rw.Header().Set("Content-Type", "application/json")
 			jsonBytes, rerr := ioutil.ReadFile("testdata/integration/files/jwks.json")
 			helper.Must(rerr)
 			b := bytes.NewBuffer(jsonBytes)
@@ -487,6 +522,7 @@ func TestOAuth2_AC_Backend(t *testing.T) {
 
 			return
 		} else if req.URL.Path == "/.well-known/openid-configuration" {
+			rw.Header().Set("Content-Type", "application/json")
 			body := []byte(`{
 			"issuer": "https://authorization.server",
 			"authorization_endpoint": "https://authorization.server/oauth2/authorize",
@@ -503,41 +539,53 @@ func TestOAuth2_AC_Backend(t *testing.T) {
 	}))
 	defer asOrigin.Close()
 
+	shutdown, hook := newCouperWithTemplate("testdata/oauth2/11_couper.hcl", helper, map[string]interface{}{"asOrigin": asOrigin.URL})
+	defer shutdown()
+
+	type backendExpectation struct {
+		path, name string
+	}
+
 	type testCase struct {
-		name        string
-		path        string
-		backendName string
+		name string
+		path string
+		exp  backendExpectation
 	}
 
+	time.Sleep(time.Second * 2) // wait for all oidc/jwks inits
+	//for _, entry := range hook.AllEntries() {
+	//	println(entry.String())
+	//}
+	//hook.Reset()
+
 	for _, tc := range []testCase{
-		{"OAuth2 Authorization Code, referenced backend", "/oauth1/redir?code=qeuboub", "as"},
-		{"OAuth2 Authorization Code, inline backend", "/oauth2/redir?code=qeuboub", "default"},
-		{"OIDC Authorization Code, referenced backend", "/oidc1/redir?code=qeuboub", "as"},
-		{"OIDC Authorization Code, inline backend", "/oidc2/redir?code=qeuboub", "default"},
+		{"OAuth2 Authorization Code, referenced backend", "/oauth1/redir?code=qeuboub", backendExpectation{"/token", "token"}},
+		{"OAuth2 Authorization Code, inline backend", "/oauth2/redir?code=qeuboub", backendExpectation{"/token", "anonymous_49_5_token_endpoint"}},
+		{"OIDC Authorization Code, referenced backend", "/oidc1/redir?code=qeuboub", backendExpectation{"/token", "token"}},
+		{"OIDC Authorization Code, inline backend", "/oidc2/redir?code=qeuboub", backendExpectation{"/token", "anonymous_78_20_token_backend"}},
 	} {
 		t.Run(tc.name, func(subT *testing.T) {
-			shutdown, hook := newCouperWithTemplate("testdata/oauth2/11_couper.hcl", test.New(t), map[string]interface{}{"asOrigin": asOrigin.URL})
-			defer shutdown()
-
-			helper := test.New(subT)
+			h := test.New(subT)
 
 			req, err := http.NewRequest(http.MethodGet, "http://back.end:8080"+tc.path, nil)
-			helper.Must(err)
+			h.Must(err)
 
 			req.Header.Set("Cookie", "pkcecv=qerbnr")
 
+			hook.Reset()
 			res, err := client.Do(req)
-			helper.Must(err)
+			h.Must(err)
 
 			if res.StatusCode != http.StatusOK {
-				subT.Errorf("expected Status %d, got: %d", http.StatusOK, res.StatusCode)
+				subT.Fatalf("expected Status %d, got: %d", http.StatusOK, res.StatusCode)
 			}
+			defer res.Body.Close()
 
 			tokenResBytes, err := io.ReadAll(res.Body)
-			helper.Must(err)
+			h.Must(err)
 
 			var jData map[string]interface{}
-			json.Unmarshal(tokenResBytes, &jData)
+			h.Must(json.Unmarshal(tokenResBytes, &jData))
 			if sub, ok := jData["sub"]; ok {
 				if sub != "myself" {
 					subT.Errorf("expected sub %q, got: %q", "myself", sub)
@@ -546,9 +594,26 @@ func TestOAuth2_AC_Backend(t *testing.T) {
 				subT.Errorf("expected sub %q, got no", "myself")
 			}
 
-			backendName := getUpstreamLogBackendName(hook)
-			if backendName != tc.backendName {
-				subT.Errorf("expected backend name %q, got: %q", tc.backendName, backendName)
+			var seen bool
+			for _, entry := range hook.AllEntries() {
+				if entry.Data["type"] == "couper_backend" && entry.Data["backend"] != "" {
+					if backend, ok := entry.Data["backend"].(string); ok {
+						if request, ok := entry.Data["request"]; ok {
+							path, _ := request.(logging.Fields)["path"].(string)
+							if reflect.DeepEqual(tc.exp, backendExpectation{
+								path, backend,
+							}) {
+								seen = true
+								break
+							}
+						}
+
+					}
+				}
+			}
+
+			if !seen {
+				subT.Errorf("expected %#v, got %q", tc.exp, getUpstreamLogBackendName(hook))
 			}
 		})
 	}
@@ -566,7 +631,7 @@ func TestOAuth2_CC_Backend(t *testing.T) {
 	client := newClient()
 	helper := test.New(t)
 
-	// authorization server creates JWT access token with sub claim from X-Sub request header
+	// authorization server creates JWT access token with sub-claim from X-Sub request header
 	asOrigin := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
 		sub := req.Header.Get("X-Sub")
 		if req.URL.Path == "/token" {
@@ -600,6 +665,9 @@ func TestOAuth2_CC_Backend(t *testing.T) {
 	}))
 	defer rsOrigin.Close()
 
+	shutdown, hook := newCouperWithTemplate("testdata/oauth2/14_couper.hcl", helper, map[string]interface{}{"asOrigin": asOrigin.URL, "rsOrigin": rsOrigin.URL})
+	defer shutdown()
+
 	type testCase struct {
 		name        string
 		path        string
@@ -607,20 +675,18 @@ func TestOAuth2_CC_Backend(t *testing.T) {
 	}
 
 	for _, tc := range []testCase{
-		{"referenced backend", "/rs1", "as"},
-		{"inline backend", "/rs2", "default"},
+		{"referenced backend", "/rs1", "token"},
+		{"inline backend", "/rs2", "anonymous_32_12"},
 	} {
 		t.Run(tc.name, func(subT *testing.T) {
-			shutdown, hook := newCouperWithTemplate("testdata/oauth2/11_couper.hcl", test.New(t), map[string]interface{}{"asOrigin": asOrigin.URL, "rsOrigin": rsOrigin.URL})
-			defer shutdown()
-
-			helper := test.New(subT)
+			h := test.New(subT)
 
 			req, err := http.NewRequest(http.MethodGet, "http://back.end:8080"+tc.path, nil)
-			helper.Must(err)
+			h.Must(err)
 
+			hook.Reset()
 			res, err := client.Do(req)
-			helper.Must(err)
+			h.Must(err)
 
 			if res.StatusCode != http.StatusNoContent {
 				subT.Errorf("expected Status %d, got: %d", http.StatusNoContent, res.StatusCode)
diff --git a/server/http_test.go b/server/http_test.go
index fac6b9ab..bf93f433 100644
--- a/server/http_test.go
+++ b/server/http_test.go
@@ -22,6 +22,7 @@ import (
 	"github.com/sirupsen/logrus"
 	logrustest "github.com/sirupsen/logrus/hooks/test"
 
+	"github.com/avenga/couper/cache"
 	"github.com/avenga/couper/config/configload"
 	"github.com/avenga/couper/config/runtime"
 	"github.com/avenga/couper/internal/test"
@@ -67,7 +68,13 @@ func TestHTTPServer_ServeHTTP_Files(t *testing.T) {
 	helper.Must(err)
 	conf.Settings.DefaultPort = 0
 
-	srvConf, err := runtime.NewServerConfiguration(conf, log.WithContext(context.TODO()), nil)
+	tmpStoreCh := make(chan struct{})
+	defer close(tmpStoreCh)
+
+	logger := log.WithContext(context.TODO())
+	tmpMemStore := cache.New(logger, tmpStoreCh)
+
+	srvConf, err := runtime.NewServerConfiguration(conf, logger, tmpMemStore)
 	helper.Must(err)
 
 	spaContent, err := os.ReadFile(conf.Servers[0].Spa.BootstrapFile)
@@ -163,7 +170,13 @@ func TestHTTPServer_ServeHTTP_Files2(t *testing.T) {
 	spaContent, err := os.ReadFile(conf.Servers[0].Spa.BootstrapFile)
 	helper.Must(err)
 
-	srvConf, err := runtime.NewServerConfiguration(conf, log.WithContext(context.TODO()), nil)
+	tmpStoreCh := make(chan struct{})
+	defer close(tmpStoreCh)
+
+	logger := log.WithContext(context.TODO())
+	tmpMemStore := cache.New(logger, tmpStoreCh)
+
+	srvConf, err := runtime.NewServerConfiguration(conf, logger, tmpMemStore)
 	helper.Must(err)
 
 	couper := server.New(ctx, conf.Context, log.WithContext(ctx), conf.Settings, &runtime.DefaultTimings, runtime.Port(0), srvConf[0])
@@ -611,7 +624,7 @@ func TestHTTPServer_RequestID(t *testing.T) {
 			helper.Must(err)
 
 			// Wait for log
-			time.Sleep(300 * time.Millisecond)
+			time.Sleep(500 * time.Millisecond)
 
 			lastLog := hook.LastEntry()
 
diff --git a/server/modifier_test.go b/server/modifier_test.go
index 3faf36ac..92f3f792 100644
--- a/server/modifier_test.go
+++ b/server/modifier_test.go
@@ -109,12 +109,12 @@ func TestIntegration_SetResponseStatus(t *testing.T) {
 		},
 		{
 			path:       "/happy-path-only",
-			expMessage: `backend error: unsupported protocol scheme "couper"`,
+			expMessage: `backend error: anonymous_40_11: unsupported protocol scheme "couper"`,
 			expStatus:  http.StatusBadGateway,
 		},
 		{
 			path:       "/inception",
-			expMessage: `backend error: unsupported protocol scheme "couper"`,
+			expMessage: `backend error: anonymous_64_15: unsupported protocol scheme "couper"`,
 			expStatus:  http.StatusBadGateway,
 		},
 	} {
diff --git a/server/testdata/integration/backends/01_couper.hcl b/server/testdata/integration/backends/01_couper.hcl
new file mode 100644
index 00000000..7f17291e
--- /dev/null
+++ b/server/testdata/integration/backends/01_couper.hcl
@@ -0,0 +1,98 @@
+server {
+  endpoint "/" {
+    proxy {
+      backend {
+        origin = "https://httpbin.org"
+        path = "/anything"
+      }
+    }
+
+    request "req" {
+      backend {
+        origin = "https://httpbin.org"
+        path = "/anything"
+      }
+    }
+  }
+
+  endpoint "/be" {
+    proxy {
+      backend = "be"
+    }
+  }
+
+  endpoint "/secure" {
+    proxy {
+      backend "be" {
+        oauth2 {
+          backend {
+            origin = "https://httpbin.org"
+            path = "/anything"
+
+            oauth2 {
+              backend {
+                origin = "https://httpbin.org"
+                path = "/anything"
+              }
+
+              client_id = "foo"
+              client_secret = "5eCr3t"
+              grant_type = "client_credentials"
+            }
+          }
+
+          client_id = "foo"
+          client_secret = "5eCr3t"
+          grant_type = "client_credentials"
+        }
+      }
+    }
+  }
+}
+
+definitions {
+  backend "be" {
+    origin = "https://httpbin.org"
+    path = "/anything"
+  }
+
+  jwt "jwt" {
+    backend {
+      origin = "https://httpbin.org"
+      path = "/anything"
+    }
+
+    header = "Authorization"
+    signature_algorithm = "HS256"
+    key = "y0urS3cretT08eU5edF0rC0uPerInThe3xamp1e"
+  }
+
+  beta_oauth2 "oauth2" {
+    backend {
+      origin = "https://httpbin.org"
+      path = "/anything"
+    }
+
+    authorization_endpoint = "https://authorization.server/oauth/authorize"
+    client_id = "foo"
+    client_secret = "5eCr3t"
+    grant_type = "authorization_code"
+    redirect_uri = "http://localhost:8085/oidc/callback"
+    token_endpoint = "https://authorization.server/oauth/token"
+    verifier_method = "ccm_s256"
+    verifier_value = "not_used_here"
+  }
+
+  beta_oidc "oidc" {
+    backend {
+      origin = "https://httpbin.org"
+      path = "/anything"
+    }
+
+    client_id = "foo"
+    client_secret = "etbinbp4in"
+    configuration_url = "{{.asOrigin}}/.well-known/openid-configuration"
+    redirect_uri = "http://localhost:8080/cb"
+    verifier_value = request.cookies.nnc
+  }
+}
diff --git a/server/testdata/integration/backend/01_couper.hcl b/server/testdata/integration/backends/02_couper.hcl
similarity index 99%
rename from server/testdata/integration/backend/01_couper.hcl
rename to server/testdata/integration/backends/02_couper.hcl
index 9a00dabc..049666df 100644
--- a/server/testdata/integration/backend/01_couper.hcl
+++ b/server/testdata/integration/backends/02_couper.hcl
@@ -20,7 +20,7 @@ server {
     request "request" {
       url = "${env.COUPER_TEST_BACKEND_ADDR}/small"
 
-      backend { 
+      backend {
         custom_log_fields = {
           request-res = backend_response.headers.content-type
           request-req = backend_request.headers.cookie
@@ -54,7 +54,7 @@ server {
       backend {
         set_response_headers = {
           x-from-request-header = backend_request.headers.x-foo
-          x-from-requests-header = backend_requests.default.headers.x-foo 
+          x-from-requests-header = backend_requests.default.headers.x-foo
           x-from-request-json-body = backend_request.json_body.a
           x-from-requests-json-body = backend_requests.default.json_body.a
           x-from-response-header = backend_response.headers.content-type
@@ -72,6 +72,7 @@ server {
           x-from-response-json-body = backend_response.json_body.Url
           x-from-responses-json-body = backend_responses.default.json_body.Url
         }
+
         oauth2 {
           grant_type = "client_credentials"
           token_endpoint = "http://localhost:8081/token"
diff --git a/server/testdata/integration/backends/03_couper.hcl b/server/testdata/integration/backends/03_couper.hcl
new file mode 100644
index 00000000..2074ac97
--- /dev/null
+++ b/server/testdata/integration/backends/03_couper.hcl
@@ -0,0 +1,47 @@
+server {
+  endpoint "/" {
+    proxy {
+      backend {
+        origin = "{{ .origin }}"
+        path = "/anonymous"
+        max_connections = 1
+      }
+    }
+  }
+
+  endpoint "/be" {
+    proxy {
+      backend = "be"
+    }
+  }
+
+  endpoint "/fake-sequence" {
+    request {
+      backend = "seq"
+    }
+
+    request "two" {
+      url = "{{ .origin }}/two"
+      backend = "seq"
+    }
+
+    request "three" {
+      url = "{{ .origin }}/three"
+      backend = "seq"
+    }
+  }
+}
+
+definitions {
+  backend "be" {
+    origin = "{{ .origin }}"
+    path = "/reference"
+    max_connections = 1
+  }
+
+  backend "seq" {
+    origin = "{{ .origin }}"
+    path = "/sequence"
+    max_connections = 1
+  }
+}
diff --git a/server/testdata/integration/config/01_couper.hcl b/server/testdata/integration/config/01_couper.hcl
index 263ee088..ac3951a4 100644
--- a/server/testdata/integration/config/01_couper.hcl
+++ b/server/testdata/integration/config/01_couper.hcl
@@ -7,10 +7,7 @@ server "hcl" {
     }
     endpoint "/expired" {
       proxy {
-        backend "b" {
-          origin = "{{ .expiredOrigin }}"
-          path = "/anything"
-        }
+        backend = "b"
       }
     }
   }
@@ -23,8 +20,8 @@ definitions {
   }
 
   backend "b" {
-    origin = "http://1.2.3.4"
-    path = "/"
+    origin = "{{ .expiredOrigin }}"
+    path = "/anything"
     disable_certificate_validation = true
   }
 
diff --git a/server/testdata/integration/config/03_couper.hcl b/server/testdata/integration/config/03_couper.hcl
index 815cabcc..8951bd96 100644
--- a/server/testdata/integration/config/03_couper.hcl
+++ b/server/testdata/integration/config/03_couper.hcl
@@ -236,16 +236,6 @@ server "acs" {
     }
   }
 
-  endpoint "/jwks/rsa/not_found" {
-    disable_access_control = ["ba1"]
-    access_control = ["JWKS_not_found"]
-    response {
-      headers = {
-        x-jwt-sub = request.context.JWKS_not_found.sub
-      }
-    }
-  }
-
   endpoint "/jwks/rsa/remote" {
     disable_access_control = ["ba1"]
     access_control = ["JWKSRemote"]
@@ -368,10 +358,7 @@ definitions {
   jwt "JWKSRemote" {
     jwks_url = "${env.COUPER_TEST_BACKEND_ADDR}/jwks.json"
   }
-  jwt "JWKS_not_found" {
-    header = "Authorization"
-    jwks_url = "${env.COUPER_TEST_BACKEND_ADDR}/not.found"
-  }
+
   jwt "JWKSBackend" {
     jwks_url = "${env.COUPER_TEST_BACKEND_ADDR}/jwks.json"
     backend {
diff --git a/server/testdata/integration/config/07_couper.hcl b/server/testdata/integration/config/07_couper.hcl
index ed51aa3f..205dc912 100644
--- a/server/testdata/integration/config/07_couper.hcl
+++ b/server/testdata/integration/config/07_couper.hcl
@@ -59,7 +59,7 @@ definitions {
   }
 
   saml "SAML" {
-    idp_metadata_file = "metadata.xml"
+    idp_metadata_file = "../accesscontrol/testdata/idp-metadata.xml"
     sp_acs_url = "http://www.examle.org/saml/acs"
     sp_entity_id = "my-sp-entity-id"
 
diff --git a/server/testdata/integration/config/12_couper.hcl b/server/testdata/integration/config/12_couper.hcl
new file mode 100644
index 00000000..fa5a4bf9
--- /dev/null
+++ b/server/testdata/integration/config/12_couper.hcl
@@ -0,0 +1,26 @@
+server {
+  endpoint "/" {
+    proxy {
+      backend = "ref"
+    }
+  }
+
+  endpoint "/prefixed" {
+    proxy {
+      backend "ref" {
+        path_prefix = "/my-prefix"
+      }
+    }
+  }
+}
+
+definitions {
+  backend "ref" {
+    origin = env.COUPER_TEST_BACKEND_ADDR
+    path = "/anything"
+  }
+}
+
+settings {
+  no_proxy_from_env = true
+}
diff --git a/server/testdata/integration/config/13_couper.hcl b/server/testdata/integration/config/13_couper.hcl
new file mode 100644
index 00000000..3432f69d
--- /dev/null
+++ b/server/testdata/integration/config/13_couper.hcl
@@ -0,0 +1,26 @@
+server {
+  endpoint "/" {
+    proxy {
+      backend = "ref"
+    }
+  }
+
+  endpoint "/granted" {
+    proxy {
+      backend "ref" {
+        basic_auth = "peter:pan"
+      }
+    }
+  }
+}
+
+definitions {
+  backend "ref" {
+    origin = env.COUPER_TEST_BACKEND_ADDR
+    path = "/anything"
+  }
+}
+
+settings {
+  no_proxy_from_env = true
+}
diff --git a/server/testdata/integration/endpoint_eval/01_couper.hcl b/server/testdata/integration/endpoint_eval/01_couper.hcl
index 5615e9de..0723dee5 100644
--- a/server/testdata/integration/endpoint_eval/01_couper.hcl
+++ b/server/testdata/integration/endpoint_eval/01_couper.hcl
@@ -9,7 +9,6 @@ server "api" {
       proxy {
         backend "anything" {
           path = "/anything"
-          origin = "http://${request.path_params.origin}"
           hostname = request.path_params.hostname
           set_response_headers = {
             x-origin = request.path_params.origin
@@ -24,6 +23,6 @@ definitions {
   # backend origin within a definition block gets replaced with the integration test "anything" server.
   backend "anything" {
     path = "/not-found-anything"
-    origin = env.COUPER_TEST_BACKEND_ADDR
+    origin = "http://${request.path_params.origin}"
   }
 }
diff --git a/server/testdata/integration/logs/01_couper.hcl b/server/testdata/integration/logs/01_couper.hcl
index 94254398..a1973269 100644
--- a/server/testdata/integration/logs/01_couper.hcl
+++ b/server/testdata/integration/logs/01_couper.hcl
@@ -209,7 +209,7 @@ definitions {
     path   = "/anything"
 
     custom_log_fields = {
-      backend = backend_responses.default.headers.server
+      backend = backend_response.headers.server
     }
   }
 
diff --git a/server/testdata/integration/validation/01_couper.hcl b/server/testdata/integration/validation/01_couper.hcl
index 52e10d84..d0070882 100644
--- a/server/testdata/integration/validation/01_couper.hcl
+++ b/server/testdata/integration/validation/01_couper.hcl
@@ -2,14 +2,14 @@ server "concurrent-requests" {
   api {
     endpoint "/**" {
       proxy {
-        backend = "test-be"
+        backend = "be"
       }
     }
   }
 }
 
 definitions {
-  backend "test-be" {
+  backend "be" {
     origin = env.COUPER_TEST_BACKEND_ADDR
     openapi {
       file = "01_schema.yaml"
diff --git a/server/testdata/oauth2/0_retries_couper.hcl b/server/testdata/oauth2/0_retries_couper.hcl
index 000b0e71..11d076c4 100644
--- a/server/testdata/oauth2/0_retries_couper.hcl
+++ b/server/testdata/oauth2/0_retries_couper.hcl
@@ -3,7 +3,6 @@ server "api" {
 
   api {
     error_file = "./testdata/integration/api_error.json"
-
     endpoint "/" {
       proxy {
         backend {
@@ -11,6 +10,7 @@ server "api" {
           path   = "/resource"
 
           oauth2 {
+            // default
             token_endpoint = "{{.asOrigin}}/oauth2"
             client_id      = "user"
             client_secret  = "pass word"
@@ -19,7 +19,7 @@ server "api" {
           }
         }
       }
-    }
+  }
 
     endpoint "/2nd" {
       proxy {
@@ -28,13 +28,13 @@ server "api" {
           path   = "/resource"
 
           oauth2 {
-            client_id      = "user"
-            client_secret  = "pass word"
-            grant_type     = "client_credentials"
-            retries        = 0
+            client_id     = "user"
+            client_secret = "pass word"
+            grant_type    = "client_credentials"
+            retries       = 0
             backend {
               origin = "{{.asOrigin}}"
-              path = "/oauth2"
+              path   = "/oauth2"
             }
           }
         }
diff --git a/server/testdata/oauth2/11_couper.hcl b/server/testdata/oauth2/11_couper.hcl
index 7c67de1d..bf2b74f5 100644
--- a/server/testdata/oauth2/11_couper.hcl
+++ b/server/testdata/oauth2/11_couper.hcl
@@ -1,34 +1,26 @@
 server "oauth-client" {
-  api {
-    endpoint "/rs1" {
-      proxy {
-        backend = "rs1"
-      }
-    }
-    endpoint "/rs2" {
-      proxy {
-        backend = "rs2"
-      }
-    }
-  }
+
   endpoint "/oauth1/redir" {
     access_control = ["ac-oauth-1"]
     response {
       json_body = request.context.ac-oauth-1
     }
   }
+
   endpoint "/oauth2/redir" {
     access_control = ["ac-oauth-2"]
     response {
       json_body = request.context.ac-oauth-2
     }
   }
+
   endpoint "/oidc1/redir" {
     access_control = ["ac-oidc-1"]
     response {
       json_body = request.context.ac-oidc-1
     }
   }
+
   endpoint "/oidc2/redir" {
     access_control = ["ac-oidc-2"]
     response {
@@ -36,48 +28,52 @@ server "oauth-client" {
     }
   }
 }
+
 definitions {
   # with referenced backend
   beta_oauth2 "ac-oauth-1" {
     authorization_endpoint = "{{.asOrigin}}/auth"
-    token_endpoint = "{{.asOrigin}}/token"
-    backend = "as"
-    client_id = "foo"
-    client_secret = "etbinbp4in"
-    grant_type = "authorization_code"
-    verifier_method = "ccm_s256"
-    verifier_value = request.cookies.pkcecv
-    redirect_uri = "http://localhost:8080/oauth/redir"
+    token_endpoint         = "{{.asOrigin}}/token"
+    backend                = "token"
+    client_id              = "foo"
+    client_secret          = "etbinbp4in"
+    grant_type             = "authorization_code"
+    verifier_method        = "ccm_s256"
+    verifier_value         = request.cookies.pkcecv
+    redirect_uri           = "http://localhost:8080/oauth/redir"
   }
+
   # with inline backend
   beta_oauth2 "ac-oauth-2" {
     authorization_endpoint = "{{.asOrigin}}/auth"
-    token_endpoint = "{{.asOrigin}}/token"
+    token_endpoint         = "{{.asOrigin}}/token"
     backend {
       origin = "{{.asOrigin}}"
       add_request_headers = {
         x-sub = "myself"
       }
     }
-    client_id = "foo"
-    client_secret = "etbinbp4in"
-    grant_type = "authorization_code"
+    client_id       = "foo"
+    client_secret   = "etbinbp4in"
+    grant_type      = "authorization_code"
     verifier_method = "ccm_s256"
-    verifier_value = request.cookies.pkcecv
-    redirect_uri = "http://localhost:8080/oauth/redir"
+    verifier_value  = request.cookies.pkcecv
+    redirect_uri    = "http://localhost:8080/oauth/redir"
   }
 
   # with referenced backend
   oidc "ac-oidc-1" {
     configuration_url = "{{.asOrigin}}/.well-known/openid-configuration"
     configuration_ttl = "1h"
-    backend = "as"
-    client_id = "foo"
-    client_secret = "etbinbp4in"
-    verifier_method = "ccm_s256"
-    verifier_value = request.cookies.pkcecv
-    redirect_uri = "http://localhost:8080/oidc/redir"
+    backend           = "configuration" # base for configuration_backend, token_backend
+    token_backend     = "token"
+    client_id         = "foo"
+    client_secret     = "etbinbp4in"
+    verifier_method   = "ccm_s256"
+    verifier_value    = request.cookies.pkcecv
+    redirect_uri      = "http://localhost:8080/oidc/redir"
   }
+
   # with inline backend
   oidc "ac-oidc-2" {
     configuration_url = "{{.asOrigin}}/.well-known/openid-configuration"
@@ -88,44 +84,22 @@ definitions {
         x-sub = "myself"
       }
     }
-    client_id = "foo"
-    client_secret = "etbinbp4in"
+    client_id       = "foo"
+    client_secret   = "etbinbp4in"
     verifier_method = "ccm_s256"
-    verifier_value = request.cookies.pkcecv
-    redirect_uri = "http://localhost:8080/oidc/redir"
+    verifier_value  = request.cookies.pkcecv
+    redirect_uri    = "http://localhost:8080/oidc/redir"
   }
 
-  # backends for resource server
-  # with referenced backend
-  backend "rs1" {
-    origin = "{{.rsOrigin}}"
-    oauth2 {
-      token_endpoint = "{{.asOrigin}}/token"
-      backend = "as"
-      client_id = "foo"
-      client_secret = "etbinbp4in"
-      grant_type = "client_credentials"
-    }
-  }
-  # with inline backend
-  backend "rs2" {
-    origin = "{{.rsOrigin}}"
-    oauth2 {
-      token_endpoint = "{{.asOrigin}}/token"
-      backend {
-        origin = "{{.asOrigin}}"
-        add_request_headers = {
-          x-sub = "myself"
-        }
-      }
-      client_id = "foo"
-      client_secret = "etbinbp4in"
-      grant_type = "client_credentials"
+  # authorization server split by context for debugging purposes
+  backend "configuration" {
+    origin = "{{.asOrigin}}"
+    add_request_headers = {
+      x-sub = "myself"
     }
   }
 
-  # backend for authorization server
-  backend "as" {
+  backend "token" {
     origin = "{{.asOrigin}}"
     add_request_headers = {
       x-sub = "myself"
@@ -133,3 +107,7 @@ definitions {
   }
 
 }
+
+settings {
+  no_proxy_from_env = true
+}
diff --git a/server/testdata/oauth2/14_couper.hcl b/server/testdata/oauth2/14_couper.hcl
new file mode 100644
index 00000000..8dbf2738
--- /dev/null
+++ b/server/testdata/oauth2/14_couper.hcl
@@ -0,0 +1,57 @@
+server "oauth-client" {
+  api {
+    endpoint "/rs1" {
+      proxy {
+        backend = "rs1"
+      }
+    }
+    endpoint "/rs2" {
+      proxy {
+        backend = "rs2"
+      }
+    }
+  }
+}
+
+definitions {
+  # backends for resource server
+  # with referenced backend
+  backend "rs1" {
+    origin = "{{.rsOrigin}}"
+    oauth2 {
+      token_endpoint = "{{.asOrigin}}/token"
+      backend        = "token"
+      client_id      = "foo"
+      client_secret  = "etbinbp4in"
+      grant_type     = "client_credentials"
+    }
+  }
+  # with inline backend
+  backend "rs2" {
+    origin = "{{.rsOrigin}}"
+    oauth2 {
+      token_endpoint = "{{.asOrigin}}/token"
+      backend {
+        origin = "{{.asOrigin}}"
+        add_request_headers = {
+          x-sub = "myself"
+        }
+      }
+      client_id     = "foo"
+      client_secret = "etbinbp4in"
+      grant_type    = "client_credentials"
+    }
+  }
+
+  backend "token" {
+    origin = "{{.asOrigin}}"
+    add_request_headers = {
+      x-sub = "myself"
+    }
+  }
+
+}
+
+settings {
+  no_proxy_from_env = true
+}