hashicorp/terraform · error

Invalid URL: must be

Error message

Invalid URL: %v must be: %v

What it means

When the optional COS backend 'endpoint' attribute is set, configure() validates it against the strict regex ^(http(s)?)://cos-internal\.([^.]+)\.tencentcos\.cn$ (4 capture groups). Any non-matching endpoint is rejected. The endpoint is intended only for TencentCloud internal (VPC) access; public access is built from region+bucket.

Solutions

  1. Use the internal form: http://cos-internal.ap-beijing.tencentcos.cn or https://cos-internal.ap-beijing.tencentcos.cn.
  2. If you want the public endpoint, omit endpoint entirely and rely on region+bucket+accelerate.

Example fix

// before
terraform {
  backend "cos" {
    endpoint = "https://bucket.cos.ap-beijing.myqcloud.com"
  }
}

// after
terraform {
  backend "cos" {
    region   = "ap-beijing"
    bucket   = "bucket"
    # endpoint omitted; or for VPC-internal access:
    # endpoint = "https://cos-internal.ap-beijing.tencentcos.cn"
  }
}
Defensive patterns

Strategy: validation

Validate before calling

// Validate the COS endpoint matches the strict internal form.
var cosInternalRe = regexp.MustCompile(`^(http(s)?)://cos-internal\.([^.]+)\.tencentcos\.cn$`)

func validateCOSEndpoint(endpoint string) error {
    if endpoint == "" {
        return nil // endpoint is optional
    }
    if len(cosInternalRe.FindStringSubmatch(endpoint)) != 4 {
        return fmt.Errorf("Invalid URL: %s must be: %s", endpoint, "http(s)://cos-internal.{Region}.tencentcos.cn")
    }
    return nil
}

Prevention

When it happens

Trigger: terraform init with endpoint set to a value that does not match http(s)://cos-internal.{Region}.tencentcos.cn.

Common situations: User supplies the public COS endpoint in endpoint (e.g. https://bucket.cos.ap-beijing.myqcloud.com); uses a custom CDN domain; forgets the cos-internal subdomain; uses http://cos.example.com.

Related errors


AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11). Data as JSON: /api/errors/0919cc1972392b7a. Report an issue: GitHub.

Appendix: source

Thrown at internal/backend/remote-state/cos/backend.go:309

		u, err = url.Parse(fmt.Sprintf("https://%s.cos.%s.myqcloud.com", b.bucket, b.region))
	}
	if err != nil {
		return err
	}

	if v, ok := data.GetOk("domain"); ok {
		b.domain = v.(string)
		log.Printf("[DEBUG] Backend: set domain for TencentCloud API client. Domain: [%s]", b.domain)
	}
	// set url as endpoint when provided
	// "http://{Bucket}.cos-internal.{Region}.tencentcos.cn"
	if v, ok := data.GetOk("endpoint"); ok {
		endpoint := v.(string)

		re := regexp.MustCompile(`^(http(s)?)://cos-internal\.([^.]+)\.tencentcos\.cn$`)
		matches := re.FindStringSubmatch(endpoint)
		if len(matches) != 4 {
			return fmt.Errorf("Invalid URL: %v must be: %v", endpoint, "http(s)://cos-internal.{Region}.tencentcos.cn")
		}

		protocol := matches[1]
		region := matches[3]

		// URL after converting
		newUrl := fmt.Sprintf("%s://%s.cos-internal.%s.tencentcos.cn", protocol, b.bucket, region)
		u, err = url.Parse(newUrl)
		log.Printf("[DEBUG] Backend: set COS URL as: [%s]", newUrl)
	}
	if err != nil {
		return err
	}

	var getProviderConfig = func(key string) string {
		var str string
		value, err := getConfigFromProfile(data, key)
		if err == nil && value != nil {

View on GitHub (pinned to d32a084675)